├── .gitignore ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── dat ├── coast.msh └── lakes.msh ├── example.py ├── img └── query.png ├── inpoly ├── __init__.py ├── inpoly2.py └── inpoly_.pyx ├── msh ├── __init__.py ├── loadmsh.py └── msh_t.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # python things 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | *.so 6 | *.dylib 7 | *.dll 8 | 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | eggs/ 13 | parts/ 14 | sdist/ 15 | var/ 16 | *.egg-info/ 17 | .installed.cfg 18 | *.egg 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | `INPOLY` is licensed under the following terms: 2 | 3 | This program may be freely redistributed under the condition that the copyright notices (including this entire header) are not removed, and no compensation is received through use of the software. Private, research, and institutional use is free. You may distribute modified versions of this code `UNDER THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE MODIFICATIONS`. Distribution of this code as part of a commercial system is permissible `ONLY BY DIRECT ARRANGEMENT WITH THE AUTHOR`. (If you are not directly supplying this code to a customer, and you are instead telling them how they can obtain it for free, then you are not required to make any arrangement with me.) 4 | 5 | `DISCLAIMER`: Neither I nor: Columbia University, the Massachusetts Institute of Technology, the University of Sydney, nor the National Aeronautics and Space Administration warrant this code in any way whatsoever. This code is provided "as-is" to be used at your own risk. 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include README.md 3 | include LICENSE.md 4 | include example.py 5 | recursive-include dat *.* 6 | recursive-include img *.* 7 | recursive-include msh *.* 8 | recursive-include inpoly *.* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## `INPOLY: Fast point(s)-in-polygon queries` 2 | 3 | A fast 'point(s)-in-polygon' routine for `Python`. 4 | 5 | `INPOLY` returns the "inside/outside" status for a set of vertices `VERT` and a general polygon (`PSLG`) embedded in the two-dimensional plane. General non-convex and multiply-connected polygonal regions can be handled. 6 | 7 |

8 | 9 |

10 | 11 | `INPOLY` is based on a 'crossing-number' test, counting the number of times a line extending from each point past the right-most region of the polygon intersects with the polygonal boundary. Points with odd counts are 'inside'. A simple implementation requires that each edge intersection be checked for each point, leading to (slow) `O(N*M)` overall complexity. 12 | 13 | This implementation seeks to improve these bounds. Query points are sorted by `y-value` and candidate intersection sets are determined via binary-search. Given a configuration with `N` test points, `M` edges and an average point-edge 'overlap' of `H`, the overall complexity scales like `O(M*H + M*LOG(N) + N*LOG(N))`, where `O(N*LOG(N))` operations are required for the initial sorting, `O(M*LOG(N))` operations are required for the set of binary-searches, and `O(M*H)` operations are required for the actual intersection tests. `H` is typically small on average, such that `H << N`. Overall, this leads to fast `O((N+M)*LOG(N))` complexity for average cases. 14 | 15 | ### `Quickstart` 16 | 17 | Clone/download + unpack this repository. 18 | python3 setup.py install 19 | python3 example.py --IDnumber=1 20 | python3 example.py --IDnumber=2 21 | python3 example.py --IDnumber=3 22 | 23 | ### `Demo problems` 24 | 25 | The following set of example problems are available in `example.py`: 26 | 27 | example: 1 # a simple box-type geometry to get started 28 | example: 2 # random queries using a common geographic dataset 29 | example: 3 # speed test vs existing inpolygon implementations 30 | 31 | Run `python3 example.py --IDnumber=N` to call the `N-th` example. 32 | 33 | ### `Fast kernels` 34 | 35 | `INPOLY` relies on `Cython` to compile the core "inpolygon" tests into a fast kernel. `inpoly_.pyx` contains the human-readable `Cython` implementation, `inpoly_.c` is the auto-generated output. For a full build: 36 | 37 | python3 setup.py build_ext --inplace 38 | python3 setup.py install 39 | 40 | These steps should "compile" the `Cython` kernel `inpoly_.pyx` into the `Python`-compatible `c`-code `inpoly_.c`, which can then be compiled into the binary lib `inpoly_.so[pyd|dylib]`. 41 | 42 | ### `License Terms` 43 | 44 | This program may be freely redistributed under the condition that the copyright notices (including this entire header) are not removed, and no compensation is received through use of the software. Private, research, and institutional use is free. You may distribute modified versions of this code `UNDER THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE MODIFICATIONS`. Distribution of this code as part of a commercial system is permissible `ONLY BY DIRECT ARRANGEMENT WITH THE AUTHOR`. (If you are not directly supplying this code to a customer, and you are instead telling them how they can obtain it for free, then you are not required to make any arrangement with me.) 45 | 46 | `DISCLAIMER`: Neither I nor the University of Sydney warrant this code in any way whatsoever. This code is provided "as-is" to be used at your own risk. 47 | 48 | ### `References` 49 | 50 | `[1]` - J. Kepner, D. Engwirda, V. Gadepally, C. Hill, T. Kraska, M. Jones, A. Kipf, L. Milechin, N. Vembar: Fast Mapping onto Census Blocks, IEEE HPEC, 2020. 51 | -------------------------------------------------------------------------------- /dat/lakes.msh: -------------------------------------------------------------------------------- 1 | #lakes.msh geometry, created by JIGSAW 2 | #https://github.com/dengwirda/jigsaw-matlab 3 | mshid=1 4 | ndims=2 5 | point=303 6 | -8.915414699999999;1.661592;0 7 | -8.884706400000001;1.5538653;0 8 | -8.7270571;1.4396306;0 9 | -8.6780329;1.3310253;0 10 | -8.2763869;1.3642085;0 11 | -7.7589437;1.5521208;0 12 | -7.3399811;1.5866412;0 13 | -7.2064877;1.7400111;0 14 | -6.987161;1.7838988;0 15 | -6.9190874;1.6751018;0 16 | -6.6594362;1.823788;0 17 | -6.4705078;2.0285109;0 18 | -6.3982459;2.0257761;0 19 | -6.1776033;2.1236043;0 20 | -5.9683636;1.9041636;0 21 | -6.0500631;1.6420917;0 22 | -6.2751037;1.4382319;0 23 | -6.2083449;1.2768143;0 24 | -6.3593873;1.1233594;0 25 | -6.4379782;0.9672913;0 26 | -6.4389687;0.940829;0 27 | -6.3308569;0.9103191;0 28 | -5.921668;1.1606956;0 29 | -5.885353;1.1594465;0 30 | -5.5306989;0.88275;0 31 | -5.4571118;0.9068911;0 32 | -5.2776995;0.8218321;0 33 | -4.6882658;1.0696109;0 34 | -4.4325551;1.115784;0 35 | -3.9867787;1.5284986;0 36 | -3.6232783;1.573356;0 37 | -3.5156848;1.5181578;0 38 | -3.1151141;1.6162773;0 39 | -2.6031886;1.9253444;0 40 | -2.4201615;2.0814581;0 41 | -2.0576784;2.182551;0 42 | -1.9469493;2.3929843;0 43 | -1.6564457;2.6016249;0 44 | -1.4385855;2.8113568;0 45 | -1.185718;2.9681802;0 46 | -0.896576;3.2839699;0 47 | -0.4656316;3.4939341;0 48 | -0.0715676;3.6520868;0 49 | 0.3221563;3.5994536;0 50 | 0.6089023;3.4943945;0 51 | 0.6092879;3.3885375;0 52 | 0.3942456;3.3878929;0 53 | 0.2866788;3.4141384;0 54 | 0.0717038;3.3345153;0 55 | 0.0896864;3.2286668;0 56 | 0.1076576;3.1757488;0 57 | -0.1436793;3.01699;0 58 | -0.3235837;2.8584538;0 59 | -0.4676923;2.7529346;0 60 | -0.9391942;2.0668111;0 61 | -1.0495193;1.7498742;0 62 | -1.0882363;1.3531376;0 63 | -1.0158452;1.3262275;0 64 | -0.9424102;1.4845979;0 65 | -0.615238;1.7477533;0 66 | -0.5061946000000001;1.9061816;0 67 | -0.2890742;2.0115316;0 68 | -0.506667;1.7473959;0 69 | -0.6160022000000001;1.5360392;0 70 | -0.397972;1.8000363;0 71 | -0.1808403;1.8525961;0 72 | 0.2894343;1.7998173;0 73 | 0.5431103;1.6681146;0 74 | 0.9058832;1.5373304;0 75 | 0.9432855999999999;1.3258124;0 76 | 1.0534256;1.1147328;0 77 | 1.1641909;0.8507940000000001;0 78 | 1.3109203;0.6930545;0 79 | 1.3117251;0.5871977;0 80 | 1.421469;0.5351345;0 81 | 1.9317215;0.5400872;0 82 | 2.2226054;0.5965222;0 83 | 2.3340522;0.4391798;0 84 | 2.4073552;0.4137156;0 85 | 2.4449497;0.334835;0 86 | 2.5909063;0.3369578;0 87 | 2.6249835;0.4962917;0 88 | 2.7343488;0.4979874;0 89 | 2.8454551;0.3938967;0 90 | 2.936647;0.3954205;0 91 | 2.9201944;0.2892562;0 92 | 2.9931921;0.2905022;0 93 | 3.3135431;0.7199108;0 94 | 3.3863589;0.7213257;0 95 | 3.9960329;1.1315466;0 96 | 4.0705567;1.0538628;0 97 | 4.6490632;1.174476;0 98 | 4.903998;1.1550887;0 99 | 5.4495532;1.1450567;0 100 | 5.9850466;1.4278414;0 101 | 6.1653805;1.4607402;0 102 | 6.3476152;1.4409063;0 103 | 6.6355959;1.5048353;0 104 | 6.5309084;1.3948027;0 105 | 6.4623697;1.2862027;0 106 | 6.4803085;0.8098819;0 107 | 6.4094979;0.7542354;0 108 | 6.410009;0.7012564;0 109 | 6.4882671;0.5981826;0 110 | 6.7068647;0.6065329;0 111 | 6.8546905;0.559329;0 112 | 6.9296957;0.5093111;0 113 | 7.3625916;0.6332439;0 114 | 7.4195041;0.5826584;0 115 | 7.4810256;0.4262426;0 116 | 7.6622;0.4606821;0 117 | 7.7339418;0.4903887;0 118 | 7.6927729;0.5946095;0 119 | 7.6199031;0.5913803;0 120 | 7.5788199;0.6956243;0 121 | 7.4649955;0.7967085;0 122 | 7.4604098;0.9025555;0 123 | 7.6668095;1.176764;0 124 | 7.770946;1.2875089;0 125 | 7.7298813;1.3917132;0 126 | 7.6707366;1.4951115;0 127 | 7.6356734;1.4670289;0 128 | 7.6246141;1.3074531;0 129 | 7.5532118;1.2777792;0 130 | 7.5280861;1.4357493;0 131 | 7.5345391;1.7011603;0 132 | 7.7131429;1.762169;0 133 | 7.8603274;1.7158544;0 134 | 7.9228745;1.9308907;0 135 | 7.7348899;2.0813402;0 136 | 7.3713821;2.1182087;0 137 | 7.2698318;1.9547751;0 138 | 7.1885795;2.1633926;0 139 | 7.1152657;2.1868122;0 140 | 7.0441691;2.15734;0 141 | 6.9336956;2.2058041;0 142 | 6.9676141;2.2602054;0 143 | 7.0648074;2.529299;0 144 | 7.0897106;2.7954185;0 145 | 7.2917362;3.1221593;0 146 | 7.3230152;3.2295634;0 147 | 7.2074219;3.4101256;0 148 | 7.0987913;3.4319941;0 149 | 7.056259;3.5892427;0 150 | 6.8349672;3.7390777;0 151 | 6.5426013;3.8863685;0 152 | 6.3538119;4.1440578;0 153 | 6.6493349;4.7917061;0 154 | 6.5385166;4.8932842;0 155 | 6.7052993;5.1650313;0 156 | 6.7009764;5.2708721;0 157 | 5.7441174;5.2345817;0 158 | 5.5686945;5.1755508;0 159 | 4.788607;5.1509481;0 160 | 4.4636817;5.3536511;0 161 | 4.2075761;5.6646354;0 162 | 3.607578;7.1858876;0 163 | 3.3873644;7.6576363;0 164 | 3.2804384;7.7613007;0 165 | 3.1762847;7.7327191;0 166 | 3.0381962;7.6506117;0 167 | 2.8286881;7.6467634;0 168 | 2.8598071;7.8590921;0 169 | 2.6845531;7.9089847;0 170 | 2.5468001;7.8008409;0 171 | 2.4437671;7.6933492;0 172 | 2.3390419;7.6917801;0 173 | 2.1983122;7.7691861;0 174 | 2.1616242;7.9010276;0 175 | 2.0918985;7.9000964;0 176 | 2.0235167;7.7933405;0 177 | 1.9540682;7.7660064;0 178 | 1.570508;7.7353016;0 179 | 1.5002116;7.7875578;0 180 | 1.2547448;7.9442315;0 181 | 0.730965;8.1526824;0 182 | 0.4522013;8.2575018;0 183 | 0.1738657;8.3098811;0 184 | -0.0694533;8.521515300000001;0 185 | -0.1737495;8.4157382;0 186 | -0.2084645;8.4422444;0 187 | -0.347615;8.363095700000001;0 188 | -0.4518238;8.389823099999999;0 189 | -0.5558115;8.4695476;0 190 | -0.5215963;8.310643900000001;0 191 | -0.5221188;8.1518584;0 192 | -0.4879606;7.9400337;0 193 | -0.2092654;7.833566;0 194 | -0.2097517;7.463066;0 195 | -0.5257651;7.0403597;0 196 | -0.5604465;7.1463352;0 197 | -0.7716285000000001;6.9354925;0 198 | -0.9473082;6.8835005;0 199 | -0.9833619;6.7249256;0 200 | -1.1593398;6.6731632;0 201 | -1.2287946;6.7795396;0 202 | -1.1574372;6.9378046;0 203 | -0.6994015;7.4115277;0 204 | -0.5933112;7.7286747;0 205 | -0.6973173;7.8878837;0 206 | -0.9759166;7.9422776;0 207 | -1.1175557;7.6256239;0 208 | -1.1194069;7.3609825;0 209 | -1.2951661;7.2564438;0 210 | -1.5483098;6.411761;0 211 | -1.9366363;6.31007;0 212 | -1.8637764;6.5209552;0 213 | -1.7582839;6.5197737;0 214 | -1.7202965;6.7840348;0 215 | -1.5427314;6.9939696;0 216 | -1.5417155;7.0998256;0 217 | -1.5762336;7.1530937;0 218 | -1.9985176;6.9989869;0 219 | -2.3506579;6.8978706;0 220 | -2.5645144;6.6893724;0 221 | -2.5351899;6.3183276;0 222 | -2.7562921;5.7395724;0 223 | -3.7980607;5.0191439;0 224 | -4.3373509;4.7676755;0 225 | -4.9834948;4.5740477;0 226 | -5.9256532;4.1282314;0 227 | -7.0382285;3.1643631;0 228 | -7.4517045;2.7580044;0 229 | -8.203390300000001;2.1245258;0 230 | -8.7235361;1.8639192;0 231 | -6.0222359;1.402651;0 232 | -5.9152018;1.5578835;0 233 | -5.8726343;1.5299217;0 234 | -5.7920161;1.5536639;0 235 | -5.8345898;1.5816055;0 236 | -5.8318773;1.6609928;0 237 | -5.663557;1.7083083;0 238 | -5.5966615;1.7590827;0 239 | -5.6808641;1.8413432;0 240 | -5.7929717;1.7391469;0 241 | -6.0818599;1.5637339;0 242 | -6.0865686;1.4314222;0 243 | -5.7324455;1.9225485;0 244 | -5.5683675;1.9594866;0 245 | -5.5335991;1.9159672;0 246 | -5.5318777;1.9688923;0 247 | -5.449816;2.0457126;0 248 | -5.4914569;2.1000333;0 249 | -5.7297704;2.0019357;0 250 | -5.7677013;1.9502324;0 251 | -5.2706487;2.2254789;0 252 | -5.2257103;2.277064;0 253 | -5.2196558;2.3563407;0 254 | -5.191165;2.4614151;0 255 | -5.2924029;2.4539566;0 256 | -5.3166868;2.3699543;0 257 | -5.3193455;2.2852741;0 258 | -5.344493;2.174812;0 259 | -2.628509;4.8904087;0 260 | -2.5231726;4.8093509;0 261 | -2.4524965;4.7818143;0 262 | -2.166425;4.9101487;0 263 | -1.8814086;4.9860466;0 264 | -2.0588911;4.9881727;0 265 | -2.093713;5.0415487;0 266 | -1.9150532;5.1452403;0 267 | -1.7011793;5.2487523;0 268 | -1.5239785;5.2470098;0 269 | -1.1676824;5.5087407;0 270 | -1.2022868;5.6148538;0 271 | -0.9185046;5.7718016;0 272 | -0.882891;5.8245348;0 273 | -0.9532116;5.8778612;0 274 | -1.3075201;5.7215257;0 275 | -1.5563954;5.5649116;0 276 | -1.6276655;5.5126727;0 277 | -1.7340945;5.4872998;0 278 | -1.840557;5.4619959;0 279 | -2.0900008;5.3326514;0 280 | -2.3756722;5.1777288;0 281 | -2.590489;5.0486276;0 282 | -0.1745905;7.648274;0 283 | -0.1048759;7.4629629;0 284 | -0.0174793;7.4629295;0 285 | -0.0349355;7.5687895;0 286 | 0.0349007;7.7275752;0 287 | 0.2267794;7.7806613;0 288 | 0.3839078;7.7280333;0 289 | 0.558318;7.7550129;0 290 | 0.6632222;7.7024853;0 291 | 0.8201638;7.7296796;0 292 | 0.732548;7.8086476;0 293 | 0.6273773;7.9405222;0 294 | 0.4880418;7.9135694;0 295 | 0.1393711;7.9922753;0 296 | 0.0348428;7.9922181;0 297 | -0.1743299;7.8864526;0 298 | 4.5964504;4.4039327;0 299 | 4.8814214;4.4120893;0 300 | 4.9866866;4.468199;0 301 | 5.1274771;4.5254771;0 302 | 5.1614199;4.5795088;0 303 | 5.0886198;4.6302255;0 304 | 4.9818982;4.6269721;0 305 | 4.8364965;4.7285905;0 306 | 4.5535316;4.6675729;0 307 | 4.3785074;4.5569131;0 308 | 4.3813116;4.4510625;0 309 | edge2=303 310 | 0;1;0 311 | 1;2;0 312 | 2;3;0 313 | 3;4;0 314 | 4;5;0 315 | 5;6;0 316 | 6;7;0 317 | 7;8;0 318 | 8;9;0 319 | 9;10;0 320 | 10;11;0 321 | 11;12;0 322 | 12;13;0 323 | 13;14;0 324 | 14;15;0 325 | 15;16;0 326 | 16;17;0 327 | 17;18;0 328 | 18;19;0 329 | 19;20;0 330 | 20;21;0 331 | 21;22;0 332 | 22;23;0 333 | 23;24;0 334 | 24;25;0 335 | 25;26;0 336 | 26;27;0 337 | 27;28;0 338 | 28;29;0 339 | 29;30;0 340 | 30;31;0 341 | 31;32;0 342 | 32;33;0 343 | 33;34;0 344 | 34;35;0 345 | 35;36;0 346 | 36;37;0 347 | 37;38;0 348 | 38;39;0 349 | 39;40;0 350 | 40;41;0 351 | 41;42;0 352 | 42;43;0 353 | 43;44;0 354 | 44;45;0 355 | 45;46;0 356 | 46;47;0 357 | 47;48;0 358 | 48;49;0 359 | 49;50;0 360 | 50;51;0 361 | 51;52;0 362 | 52;53;0 363 | 53;54;0 364 | 54;55;0 365 | 55;56;0 366 | 56;57;0 367 | 57;58;0 368 | 58;59;0 369 | 59;60;0 370 | 60;61;0 371 | 61;62;0 372 | 62;63;0 373 | 63;64;0 374 | 64;65;0 375 | 65;66;0 376 | 66;67;0 377 | 67;68;0 378 | 68;69;0 379 | 69;70;0 380 | 70;71;0 381 | 71;72;0 382 | 72;73;0 383 | 73;74;0 384 | 74;75;0 385 | 75;76;0 386 | 76;77;0 387 | 77;78;0 388 | 78;79;0 389 | 79;80;0 390 | 80;81;0 391 | 81;82;0 392 | 82;83;0 393 | 83;84;0 394 | 84;85;0 395 | 85;86;0 396 | 86;87;0 397 | 87;88;0 398 | 88;89;0 399 | 89;90;0 400 | 90;91;0 401 | 91;92;0 402 | 92;93;0 403 | 93;94;0 404 | 94;95;0 405 | 95;96;0 406 | 96;97;0 407 | 97;98;0 408 | 98;99;0 409 | 99;100;0 410 | 100;101;0 411 | 101;102;0 412 | 102;103;0 413 | 103;104;0 414 | 104;105;0 415 | 105;106;0 416 | 106;107;0 417 | 107;108;0 418 | 108;109;0 419 | 109;110;0 420 | 110;111;0 421 | 111;112;0 422 | 112;113;0 423 | 113;114;0 424 | 114;115;0 425 | 115;116;0 426 | 116;117;0 427 | 117;118;0 428 | 118;119;0 429 | 119;120;0 430 | 120;121;0 431 | 121;122;0 432 | 122;123;0 433 | 123;124;0 434 | 124;125;0 435 | 125;126;0 436 | 126;127;0 437 | 127;128;0 438 | 128;129;0 439 | 129;130;0 440 | 130;131;0 441 | 131;132;0 442 | 132;133;0 443 | 133;134;0 444 | 134;135;0 445 | 135;136;0 446 | 136;137;0 447 | 137;138;0 448 | 138;139;0 449 | 139;140;0 450 | 140;141;0 451 | 141;142;0 452 | 142;143;0 453 | 143;144;0 454 | 144;145;0 455 | 145;146;0 456 | 146;147;0 457 | 147;148;0 458 | 148;149;0 459 | 149;150;0 460 | 150;151;0 461 | 151;152;0 462 | 152;153;0 463 | 153;154;0 464 | 154;155;0 465 | 155;156;0 466 | 156;157;0 467 | 157;158;0 468 | 158;159;0 469 | 159;160;0 470 | 160;161;0 471 | 161;162;0 472 | 162;163;0 473 | 163;164;0 474 | 164;165;0 475 | 165;166;0 476 | 166;167;0 477 | 167;168;0 478 | 168;169;0 479 | 169;170;0 480 | 170;171;0 481 | 171;172;0 482 | 172;173;0 483 | 173;174;0 484 | 174;175;0 485 | 175;176;0 486 | 176;177;0 487 | 177;178;0 488 | 178;179;0 489 | 179;180;0 490 | 180;181;0 491 | 181;182;0 492 | 182;183;0 493 | 183;184;0 494 | 184;185;0 495 | 185;186;0 496 | 186;187;0 497 | 187;188;0 498 | 188;189;0 499 | 189;190;0 500 | 190;191;0 501 | 191;192;0 502 | 192;193;0 503 | 193;194;0 504 | 194;195;0 505 | 195;196;0 506 | 196;197;0 507 | 197;198;0 508 | 198;199;0 509 | 199;200;0 510 | 200;201;0 511 | 201;202;0 512 | 202;203;0 513 | 203;204;0 514 | 204;205;0 515 | 205;206;0 516 | 206;207;0 517 | 207;208;0 518 | 208;209;0 519 | 209;210;0 520 | 210;211;0 521 | 211;212;0 522 | 212;213;0 523 | 213;214;0 524 | 214;215;0 525 | 215;216;0 526 | 216;217;0 527 | 217;218;0 528 | 218;219;0 529 | 219;220;0 530 | 220;221;0 531 | 221;222;0 532 | 222;223;0 533 | 223;224;0 534 | 224;0;0 535 | 225;226;0 536 | 226;227;0 537 | 227;228;0 538 | 228;229;0 539 | 229;230;0 540 | 230;231;0 541 | 231;232;0 542 | 232;233;0 543 | 233;234;0 544 | 234;235;0 545 | 235;236;0 546 | 236;225;0 547 | 237;238;0 548 | 238;239;0 549 | 239;240;0 550 | 240;241;0 551 | 241;242;0 552 | 242;243;0 553 | 243;244;0 554 | 244;237;0 555 | 245;246;0 556 | 246;247;0 557 | 247;248;0 558 | 248;249;0 559 | 249;250;0 560 | 250;251;0 561 | 251;252;0 562 | 252;245;0 563 | 253;254;0 564 | 254;255;0 565 | 255;256;0 566 | 256;257;0 567 | 257;258;0 568 | 258;259;0 569 | 259;260;0 570 | 260;261;0 571 | 261;262;0 572 | 262;263;0 573 | 263;264;0 574 | 264;265;0 575 | 265;266;0 576 | 266;267;0 577 | 267;268;0 578 | 268;269;0 579 | 269;270;0 580 | 270;271;0 581 | 271;272;0 582 | 272;273;0 583 | 273;274;0 584 | 274;275;0 585 | 275;253;0 586 | 276;277;0 587 | 277;278;0 588 | 278;279;0 589 | 279;280;0 590 | 280;281;0 591 | 281;282;0 592 | 282;283;0 593 | 283;284;0 594 | 284;285;0 595 | 285;286;0 596 | 286;287;0 597 | 287;288;0 598 | 288;289;0 599 | 289;290;0 600 | 290;291;0 601 | 291;276;0 602 | 292;293;0 603 | 293;294;0 604 | 294;295;0 605 | 295;296;0 606 | 296;297;0 607 | 297;298;0 608 | 298;299;0 609 | 299;300;0 610 | 300;301;0 611 | 301;302;0 612 | 302;292;0 613 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import time 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | import matplotlib.path as mpltPath 7 | 8 | from inpoly import inpoly2 9 | from msh import jigsaw_msh_t, loadmsh 10 | 11 | import argparse 12 | 13 | 14 | def ex_1(args): 15 | 16 | #-- Example 1: set up simple boxes and run points-in-polygon 17 | #-- queries. Boxes defined as a "collection" of polygons and 18 | #-- query points include "exact" matches. 19 | 20 | node = np.array([ 21 | [4, 0], [8, 4], [4, 8], [0, 4], [3, 3], 22 | [5, 3], [5, 5], [3, 5]]) 23 | 24 | edge = np.array([ 25 | [0, 1], [1, 2], [2, 3], [3, 0], [4, 5], 26 | [5, 6], [6, 7], [7, 4]]) 27 | 28 | xpos, ypos = np.meshgrid( 29 | np.linspace(-1, 9, 51), np.linspace(-1, 9, 51)) 30 | 31 | points = np.concatenate(( 32 | np.reshape(xpos, (xpos.size, 1)), 33 | np.reshape(ypos, (ypos.size, 1))), axis=1) 34 | 35 | IN, ON = inpoly2(points, node, edge) 36 | 37 | if (not args.showplot): return 38 | 39 | fig, ax = plt.subplots() 40 | plt.plot(points[IN==1, 0], points[IN==1, 1], "b.") 41 | plt.plot(points[IN==0, 0], points[IN==0, 1], "r.") 42 | plt.plot(points[ON==1, 0], points[ON==1, 1], "ms") 43 | 44 | ax.set_aspect("equal", adjustable="box") 45 | plt.show() 46 | 47 | 48 | def ex_2(args): 49 | 50 | #-- Example 2: load the lake superior geometry and test wrt. 51 | #-- random query points, input nodes + edge centres. 52 | 53 | geom = jigsaw_msh_t() 54 | loadmsh(os.path.join("dat", "lakes.msh"), geom) 55 | 56 | node = geom.point["coord"] 57 | edge = geom.edge2["index"] 58 | 59 | emid = ( 60 | 0.5 * node[edge[:, 0], :] + 61 | 0.5 * node[edge[:, 1], :] 62 | ) 63 | 64 | rpts = np.random.rand(7500, 2) 65 | 66 | nmax = np.max(node, axis=0) 67 | nmin = np.min(node, axis=0) 68 | diff = (nmax - nmin) 69 | half = (nmin + nmax) / 2.0 70 | 71 | rpts[:, 0] = (rpts[:, 0] - .5) * diff[0] + half[0] 72 | rpts[:, 1] = (rpts[:, 1] - .5) * diff[1] + half[1] 73 | 74 | points = np.concatenate((node, emid, rpts)) 75 | 76 | IN, ON = inpoly2(points, node, edge) 77 | 78 | if (not args.showplot): return 79 | 80 | fig, ax = plt.subplots() 81 | plt.plot(points[IN==1, 0], points[IN==1, 1], "b.") 82 | plt.plot(points[IN==0, 0], points[IN==0, 1], "r.") 83 | plt.plot(points[ON==1, 0], points[ON==1, 1], "ms") 84 | 85 | ax.set_aspect("equal", adjustable="box") 86 | plt.show() 87 | 88 | 89 | def ex_3(args): 90 | 91 | #-- Example 3: load geom. from geographic data and test wrt. 92 | #-- random query points, input nodes + edge centres. 93 | 94 | #-- Compare run-times against the matplotlib implementation. 95 | 96 | geom = jigsaw_msh_t() 97 | loadmsh(os.path.join("dat", "coast.msh"), geom) 98 | 99 | node = geom.point["coord"] 100 | edge = geom.edge2["index"] 101 | 102 | emid = ( 103 | 0.5 * node[edge[:, 0], :] + 104 | 0.5 * node[edge[:, 1], :] 105 | ) 106 | 107 | rpts = np.random.rand(25000, 2) 108 | 109 | nmax = np.max(node, axis=0) 110 | nmin = np.min(node, axis=0) 111 | diff = (nmax - nmin) 112 | half = (nmin + nmax) / 2.0 113 | 114 | rpts[:, 0] = (rpts[:, 0] - .5) * diff[0] + half[0] 115 | rpts[:, 1] = (rpts[:, 1] - .5) * diff[1] + half[1] 116 | 117 | points = np.concatenate((node, emid, rpts)) 118 | 119 | ttic = time.time() 120 | 121 | path = mpltPath.Path(node) 122 | IN = path.contains_points(points) 123 | 124 | ttoc = time.time() 125 | print("PLTPATH: ", ttoc - ttic) 126 | 127 | ttic = time.time() 128 | 129 | IN, ON = inpoly2(points, node, edge) 130 | 131 | ttoc = time.time() 132 | print("INPOLY2: ", ttoc - ttic) 133 | 134 | if (not args.showplot): return 135 | 136 | fig, ax = plt.subplots() 137 | plt.plot(points[IN==1, 0], points[IN==1, 1], "b.") 138 | plt.plot(points[IN==0, 0], points[IN==0, 1], "r.") 139 | plt.plot(points[ON==1, 0], points[ON==1, 1], "ms") 140 | 141 | ax.set_aspect("equal", adjustable="box") 142 | plt.show() 143 | 144 | 145 | def strtobool(val): 146 | """ 147 | Silly re-implementation of strtobool, since python has 148 | deprecated these things... 149 | 150 | """ 151 | val = val.lower() 152 | if val in ('y', 'yes', 't', 'true', 'on', '1'): 153 | return 1 154 | elif val in ('n', 'no', 'f', 'false', 'off', '0'): 155 | return 0 156 | else: 157 | raise ValueError("Invalid bool %r" % (val,)) 158 | 159 | 160 | if (__name__ == "__main__"): 161 | parser = argparse.ArgumentParser( 162 | description=__doc__, formatter_class=argparse.RawTextHelpFormatter) 163 | 164 | parser.add_argument("--IDnumber", dest="IDnumber", type=int, 165 | default=1, 166 | required=False, help="Run example with ID = (1-3)") 167 | 168 | parser.add_argument("--showplot", dest="showplot", 169 | type=lambda x: bool(strtobool(x)), 170 | default=True, 171 | required=False, help="True to draw fig. to screen") 172 | 173 | args = parser.parse_args() 174 | 175 | if (args.IDnumber == 1): ex_1(args) 176 | if (args.IDnumber == 2): ex_2(args) 177 | if (args.IDnumber == 3): ex_3(args) 178 | -------------------------------------------------------------------------------- /img/query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengwirda/inpoly-python/cf5983950ef54d7f85715756dd06ec54abd368e1/img/query.png -------------------------------------------------------------------------------- /inpoly/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from inpoly.inpoly2 import inpoly2 3 | -------------------------------------------------------------------------------- /inpoly/inpoly2.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | 5 | def inpoly2(vert, node, edge=None, ftol=5.0e-14): 6 | """ 7 | INPOLY2: compute "points-in-polygon" queries. 8 | 9 | STAT = INPOLY2(VERT, NODE, EDGE) returns the "inside/ou- 10 | tside" status for a set of vertices VERT and a polygon 11 | NODE, EDGE embedded in a two-dimensional plane. General 12 | non-convex and multiply-connected polygonal regions can 13 | be handled. VERT is an N-by-2 array of XY coordinates to 14 | be tested. STAT is an associated N-by-1 boolean array, 15 | with STAT[II] = TRUE if VERT[II, :] is an inside point. 16 | 17 | The polygonal region is defined as a piecewise-straight- 18 | line-graph, where NODE is an M-by-2 array of polygon ve- 19 | rtices and EDGE is a P-by-2 array of edge indexing. Each 20 | row in EDGE represents an edge of the polygon, such that 21 | NODE[EDGE[KK, 0], :] and NODE[EDGE[KK, 2], :] are the 22 | coordinates of the endpoints of the KK-TH edge. If the 23 | argument EDGE is omitted it assumed that the vertices in 24 | NODE are connected in ascending order. 25 | 26 | STAT, BNDS = INPOLY2(..., FTOL) also returns an N-by-1 27 | boolean array BNDS, with BNDS[II] = TRUE if VERT[II, :] 28 | lies "on" a boundary segment, where FTOL is a floating- 29 | point tolerance for boundary comparisons. By default, 30 | FTOL ~ EPS ^ 0.85. 31 | 32 | -------------------------------------------------------- 33 | 34 | This algorithm is based on a "crossing-number" test, 35 | counting the number of times a line extending from each 36 | point past the right-most end of the polygon intersects 37 | with the polygonal boundary. Points with odd counts are 38 | "inside". A simple implementation requires that each 39 | edge intersection be checked for each point, leading to 40 | O(N*M) complexity... 41 | 42 | This implementation seeks to improve these bounds: 43 | 44 | * Sorting the query points by y-value and determining can- 45 | didate edge intersection sets via binary-search. Given a 46 | configuration with N test points, M edges and an average 47 | point-edge "overlap" of H, the overall complexity scales 48 | like O(M*H + M*LOG(N) + N*LOG(N)), where O(N*LOG(N)) 49 | operations are required for sorting, O(M*LOG(N)) operat- 50 | ions required for the set of binary-searches, and O(M*H) 51 | operations required for the intersection tests, where H 52 | is typically small on average, such that H << N. 53 | 54 | * Carefully checking points against the bounding-box asso- 55 | ciated with each polygon edge. This minimises the number 56 | of calls to the (relatively) expensive edge intersection 57 | test. 58 | 59 | Updated: 19 Dec, 2020 60 | 61 | Authors: Darren Engwirda, Keith Roberts 62 | 63 | """ 64 | 65 | vert = np.asarray(vert, dtype=np.float64) 66 | node = np.asarray(node, dtype=np.float64) 67 | 68 | STAT = np.full( 69 | vert.shape[0], False, dtype=bool) 70 | BNDS = np.full( 71 | vert.shape[0], False, dtype=bool) 72 | 73 | if node.size == 0: return STAT, BNDS 74 | 75 | if edge is None: 76 | #----------------------------------- set edges if not passed 77 | indx = np.arange(0, node.shape[0] - 1) 78 | 79 | edge = np.zeros(( 80 | node.shape[0], +2), dtype=np.int32) 81 | 82 | edge[:-1, 0] = indx + 0 83 | edge[:-1, 1] = indx + 1 84 | edge[ -1, 0] = node.shape[0] - 1 85 | 86 | else: 87 | edge = np.asarray(edge, dtype=np.int32) 88 | 89 | #----------------------------------- prune points using bbox 90 | used = edge.ravel() 91 | xmin = np.nanmin(node[used, 0]) 92 | xmax = np.nanmax(node[used, 0]) 93 | ymin = np.nanmin(node[used, 1]) 94 | ymax = np.nanmax(node[used, 1]) 95 | xdel = xmax - xmin 96 | ydel = ymax - ymin 97 | 98 | lbar = (xdel + ydel) / 2.0 99 | 100 | veps = (lbar * ftol) 101 | 102 | mask = np.logical_and.reduce(( 103 | vert[:, 0] >= xmin - veps, 104 | vert[:, 1] >= ymin - veps, 105 | vert[:, 0] <= xmax + veps, 106 | vert[:, 1] <= ymax + veps) 107 | ) 108 | 109 | vert = vert[mask] 110 | 111 | if vert.size == 0: return STAT, BNDS 112 | 113 | #------------------ flip to ensure y-axis is the `long` axis 114 | xdel = np.amax(vert[:, 0]) - np.amin(vert[:, 0]) 115 | ydel = np.amax(vert[:, 1]) - np.amin(vert[:, 1]) 116 | 117 | lbar = (xdel + ydel) / 2.0 118 | 119 | if (xdel > ydel): 120 | vert = vert[:, (1, 0)] 121 | node = node[:, (1, 0)] 122 | 123 | #----------------------------------- sort points via y-value 124 | swap = node[edge[:, 1], 1] < node[edge[:, 0], 1] 125 | temp = edge[swap] 126 | edge[swap, :] = temp[:, (1, 0)] 127 | 128 | #----------------------------------- call crossing-no kernel 129 | stat, bnds = \ 130 | _inpoly(vert, node, edge, ftol, lbar) 131 | 132 | #----------------------------------- unpack array reindexing 133 | STAT[mask] = stat 134 | BNDS[mask] = bnds 135 | 136 | return STAT, BNDS 137 | 138 | 139 | def _inpoly(vert, node, edge, ftol, lbar): 140 | """ 141 | _INPOLY: the local pycode version of the crossing-number 142 | test. Loop over edges; do a binary-search for the first 143 | vertex that intersects with the edge y-range; crossing- 144 | number comparisons; break when the local y-range is met. 145 | 146 | """ 147 | 148 | feps = ftol * (lbar ** +1) 149 | veps = ftol * (lbar ** +1) 150 | 151 | stat = np.full( 152 | vert.shape[0], False, dtype=bool) 153 | bnds = np.full( 154 | vert.shape[0], False, dtype=bool) 155 | 156 | #----------------------------------- compute y-range overlap 157 | ivec = np.argsort(vert[:, 1], kind="quicksort") 158 | 159 | XONE = node[edge[:, 0], 0] 160 | XTWO = node[edge[:, 1], 0] 161 | YONE = node[edge[:, 0], 1] 162 | YTWO = node[edge[:, 1], 1] 163 | 164 | XMIN = np.minimum(XONE, XTWO) 165 | XMAX = np.maximum(XONE, XTWO) 166 | 167 | XMIN = XMIN - veps 168 | XMAX = XMAX + veps 169 | YMIN = YONE - veps 170 | YMAX = YTWO + veps 171 | 172 | YDEL = YTWO - YONE 173 | XDEL = XTWO - XONE 174 | 175 | EDEL = np.abs(XDEL) + YDEL 176 | 177 | ione = np.searchsorted( 178 | vert[:, 1], YMIN, "left", sorter=ivec) 179 | itwo = np.searchsorted( 180 | vert[:, 1], YMAX, "right", sorter=ivec) 181 | 182 | #----------------------------------- loop over polygon edges 183 | for epos in range(edge.shape[0]): 184 | 185 | xone = XONE[epos]; xtwo = XTWO[epos] 186 | yone = YONE[epos]; ytwo = YTWO[epos] 187 | 188 | xmin = XMIN[epos]; xmax = XMAX[epos] 189 | 190 | edel = EDEL[epos] 191 | 192 | xdel = XDEL[epos]; ydel = YDEL[epos] 193 | 194 | #------------------------------- calc. edge-intersection 195 | for jpos in range(ione[epos], itwo[epos]): 196 | 197 | jvrt = ivec[jpos] 198 | 199 | if bnds[jvrt]: continue 200 | 201 | xpos = vert[jvrt, 0] 202 | ypos = vert[jvrt, 1] 203 | 204 | if xpos >= xmin: 205 | if xpos <= xmax: 206 | #------------------- compute crossing number 207 | mul1 = ydel * (xpos - xone) 208 | mul2 = xdel * (ypos - yone) 209 | 210 | if feps * edel >= abs(mul2 - mul1): 211 | #------------------- BNDS -- approx. on edge 212 | bnds[jvrt] = True 213 | stat[jvrt] = True 214 | 215 | elif (ypos == yone) and (xpos == xone): 216 | #------------------- BNDS -- match about ONE 217 | bnds[jvrt] = True 218 | stat[jvrt] = True 219 | 220 | elif (ypos == ytwo) and (xpos == xtwo): 221 | #------------------- BNDS -- match about TWO 222 | bnds[jvrt] = True 223 | stat[jvrt] = True 224 | 225 | elif (mul1 <= mul2) and (ypos >= yone) \ 226 | and (ypos < ytwo): 227 | #------------------- advance crossing number 228 | stat[jvrt] = not stat[jvrt] 229 | 230 | elif (ypos >= yone) and (ypos < ytwo): 231 | #----------------------- advance crossing number 232 | stat[jvrt] = not stat[jvrt] 233 | 234 | return stat, bnds 235 | 236 | 237 | try: 238 | #-- automagically "override" _inpoly with a compiled kernel! 239 | from inpoly.inpoly_ import _inpoly # noqa 240 | 241 | except ImportError: 242 | #-- if it hasn't been built, just stick with the .py version 243 | pass 244 | -------------------------------------------------------------------------------- /inpoly/inpoly_.pyx: -------------------------------------------------------------------------------- 1 | 2 | #cython: language_level=3 3 | #cython: boundscheck=False 4 | #cython: wraparound=False 5 | #cython: nonecheck=False 6 | #cython: cdivision=True 7 | #cython: cpow=True 8 | 9 | import numpy as np 10 | cimport numpy as np 11 | cimport cython 12 | 13 | def _inpoly(np.ndarray[double, ndim=+2] vert, 14 | np.ndarray[double, ndim=+2] node, 15 | np.ndarray[np.int32_t, ndim=+2] edge, 16 | const double ftol, const double lbar): 17 | """ 18 | _INPOLY: the local cython version of the crossing-number 19 | test. Loop over edges; do a binary-search for the first 20 | vertex that intersects with the edge y-range; crossing- 21 | number comparisons; break when the local y-range is met. 22 | 23 | Updated: 19 December, 2020 24 | 25 | Authors: Darren Engwirda, Keith Roberts 26 | 27 | """ 28 | cdef size_t epos, jpos, inod, jnod, jvrt 29 | cdef double feps, veps 30 | cdef double xone, xtwo, xmin, xmax, xdel 31 | cdef double yone, ytwo, ymin, ymax, ydel 32 | cdef double xpos, ypos, mul1, mul2 33 | 34 | feps = ftol * (lbar ** +1) # local bnds reltol 35 | veps = ftol * (lbar ** +1) 36 | 37 | cdef size_t vnum = vert.shape[0] 38 | cdef size_t enum = edge.shape[0] 39 | 40 | cdef np.ndarray[np.int8_t] stat = np.full( 41 | vnum, +0, dtype=np.int8) 42 | 43 | cdef np.ndarray[np.int8_t] bnds = np.full( 44 | vnum, +0, dtype=np.int8) 45 | 46 | cdef np.int8_t *sptr = &stat[+0] # ptr to contiguous 47 | cdef np.int8_t *bptr = &bnds[+0] 48 | 49 | #----------------------------------- compute y-range overlap 50 | cdef np.ndarray[Py_ssize_t] ivec = \ 51 | np.argsort(vert[:, 1], kind = "quicksort") 52 | 53 | YMIN = node[edge[:, 0], 1] - veps 54 | 55 | cdef np.ndarray[Py_ssize_t] head = \ 56 | np.searchsorted( 57 | vert[:, 1], YMIN, "left", sorter=ivec) 58 | 59 | cdef const Py_ssize_t *iptr = &ivec[+0] 60 | cdef const Py_ssize_t *hptr = &head[+0] 61 | 62 | #----------------------------------- loop over polygon edges 63 | for epos in range(enum): 64 | 65 | inod = edge[epos, 0] # unpack *this edge 66 | jnod = edge[epos, 1] 67 | 68 | xone = node[inod, 0] 69 | xtwo = node[jnod, 0] 70 | yone = node[inod, 1] 71 | ytwo = node[jnod, 1] 72 | 73 | xmin = min(xone, xtwo) # compute edge bbox 74 | xmax = max(xone, xtwo) 75 | 76 | xmin = xmin - veps 77 | xmax = xmax + veps 78 | ymax = ytwo + veps 79 | 80 | xdel = xtwo - xone 81 | ydel = ytwo - yone 82 | 83 | edel = abs(xdel) + ydel 84 | 85 | #------------------------------- calc. edge-intersection 86 | for jpos in range(hptr[epos], vnum): 87 | 88 | jvrt = iptr[jpos] 89 | 90 | if bptr[jvrt]: continue 91 | 92 | xpos = vert[jvrt, 0] 93 | ypos = vert[jvrt, 1] 94 | 95 | if ypos >= ymax: break # due to the y-sort 96 | 97 | if xpos >= xmin: 98 | if xpos <= xmax: 99 | #------------------- compute crossing number 100 | mul1 = ydel * (xpos - xone) 101 | mul2 = xdel * (ypos - yone) 102 | 103 | if feps * edel >= abs(mul2 - mul1): 104 | #------------------- BNDS -- approx. on edge 105 | bptr[jvrt] = 1 106 | sptr[jvrt] = 1 107 | 108 | elif (ypos == yone) and (xpos == xone): 109 | #------------------- BNDS -- match about ONE 110 | bptr[jvrt] = 1 111 | sptr[jvrt] = 1 112 | 113 | elif (ypos == ytwo) and (xpos == xtwo): 114 | #------------------- BNDS -- match about TWO 115 | bptr[jvrt] = 1 116 | sptr[jvrt] = 1 117 | 118 | elif (mul1 <= mul2) and (ypos >= yone) \ 119 | and (ypos < ytwo): 120 | #------------------- advance crossing number 121 | sptr[jvrt] = 1 - sptr[jvrt] 122 | 123 | elif (ypos >= yone) and (ypos < ytwo): 124 | #----------------------- advance crossing number 125 | sptr[jvrt] = 1 - sptr[jvrt] 126 | 127 | return stat, bnds 128 | -------------------------------------------------------------------------------- /msh/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from msh.msh_t import jigsaw_msh_t 3 | from msh.loadmsh import loadmsh 4 | -------------------------------------------------------------------------------- /msh/loadmsh.py: -------------------------------------------------------------------------------- 1 | 2 | from pathlib import Path 3 | import numpy as np 4 | from numpy.lib import recfunctions as rfn 5 | 6 | from msh.msh_t import jigsaw_msh_t 7 | 8 | #-- snipped from: github.com/dengwirda/jigsaw-python 9 | 10 | 11 | def loadmshid(mesh, fptr, ltag): 12 | """ 13 | LOADMSHID: load the MSHID data segment from file. 14 | 15 | """ 16 | data = ltag[1].split(";") 17 | 18 | if (len(data) > 1): 19 | mesh.mshID = data[1].strip().lower() 20 | 21 | return 22 | 23 | 24 | def loadradii(mesh, fptr, ltag): 25 | """ 26 | LOADRADII: load the RADII data segment from file. 27 | 28 | """ 29 | rtag = ltag[1].split(";") 30 | 31 | mesh.radii = np.empty( 32 | +3, dtype=jigsaw_msh_t.REALS_t) 33 | 34 | if (len(rtag) == +1): 35 | mesh.radii[0] = float(rtag[0]) 36 | mesh.radii[1] = float(rtag[0]) 37 | mesh.radii[2] = float(rtag[0]) 38 | 39 | elif (len(rtag) == +3): 40 | mesh.radii[0] = float(rtag[0]) 41 | mesh.radii[1] = float(rtag[1]) 42 | mesh.radii[2] = float(rtag[2]) 43 | 44 | else: 45 | raise Exception("Invalid RADII: " + ltag) 46 | 47 | return 48 | 49 | 50 | def loadvert2(fptr, ltag): 51 | """ 52 | LOADVERT2: load the 2-dim. vertex pos. from file. 53 | 54 | """ 55 | lnum = int(ltag[1]); vnum = 3 56 | 57 | data = [] 58 | for line in range(lnum): 59 | data.append(fptr.readline()) 60 | 61 | data = " ".join(data).replace("\n", ";") 62 | 63 | vert = np.fromstring( 64 | data, dtype=np.float64, sep=";") 65 | 66 | vert = np.reshape( 67 | vert, (lnum, vnum, ), order="C") 68 | 69 | vert = rfn.unstructured_to_structured( 70 | vert, dtype=jigsaw_msh_t.VERT2_t, align=True) 71 | 72 | return vert 73 | 74 | 75 | def loadvert3(fptr, ltag): 76 | """ 77 | LOADVERT3: load the 3-dim. vertex pos. from file. 78 | 79 | """ 80 | lnum = int(ltag[1]); vnum = 4 81 | 82 | data = [] 83 | for line in range(lnum): 84 | data.append(fptr.readline()) 85 | 86 | data = " ".join(data).replace("\n", ";") 87 | 88 | vert = np.fromstring( 89 | data, dtype=np.float64, sep=";") 90 | 91 | vert = np.reshape( 92 | vert, (lnum, vnum, ), order="C") 93 | 94 | vert = rfn.unstructured_to_structured( 95 | vert, dtype=jigsaw_msh_t.VERT3_t, align=True) 96 | 97 | return vert 98 | 99 | 100 | def loadpoint(mesh, fptr, ltag): 101 | """ 102 | LOADPOINT: load the POINT data segment from file. 103 | 104 | """ 105 | if (mesh.ndims == +2): 106 | mesh.vert2 = loadvert2(fptr, ltag) 107 | 108 | elif (mesh.ndims == +3): 109 | mesh.vert3 = loadvert3(fptr, ltag) 110 | 111 | else: 112 | raise Exception("Invalid NDIMS: " + ltag) 113 | 114 | return 115 | 116 | 117 | def loadseeds(mesh, fptr, ltag): 118 | """ 119 | LOADSEEDS: load the SEEDS data segment from file. 120 | 121 | """ 122 | if (mesh.ndims == +2): 123 | mesh.seed2 = loadvert2(fptr, ltag) 124 | 125 | elif (mesh.ndims == +3): 126 | mesh.seed3 = loadvert3(fptr, ltag) 127 | 128 | else: 129 | raise Exception("Invalid NDIMS: " + ltag) 130 | 131 | return 132 | 133 | 134 | def loadarray(fptr, ltag): 135 | """ 136 | LOADARRAY: load the ARRAY data segment from file. 137 | 138 | """ 139 | vtag = ltag[1].split(";") 140 | 141 | lnum = int(vtag[0]) 142 | vnum = int(vtag[1]) 143 | 144 | data = [] 145 | for line in range(lnum): 146 | data.append(fptr.readline()) 147 | 148 | data = " ".join(data).replace("\n", ";") 149 | 150 | vals = np.fromstring( 151 | data, dtype=np.float64, sep=";") 152 | 153 | vals = np.squeeze(np.reshape( 154 | vals, (lnum, vnum, ), order="C")) 155 | 156 | return vals 157 | 158 | 159 | def loadpower(mesh, fptr, ltag): 160 | """ 161 | LOADPOWER: load the POWER data segment from file. 162 | 163 | """ 164 | mesh.power = loadarray(fptr, ltag) 165 | 166 | return 167 | 168 | 169 | def loadvalue(mesh, fptr, ltag): 170 | """ 171 | LOADVALUE: load the VALUE data segment from file. 172 | 173 | """ 174 | mesh.value = loadarray(fptr, ltag) 175 | 176 | return 177 | 178 | 179 | def loadslope(mesh, fptr, ltag): 180 | """ 181 | LOADSLOPE: load the SLOPE data segment from file. 182 | 183 | """ 184 | mesh.slope = loadarray(fptr, ltag) 185 | 186 | return 187 | 188 | 189 | def loadedge2(mesh, fptr, ltag): 190 | """ 191 | LOADEDGE2: load the EDGE2 data segment from file. 192 | 193 | """ 194 | lnum = int(ltag[1]); vnum = 3 195 | 196 | data = [] 197 | for line in range(lnum): 198 | data.append(fptr.readline()) 199 | 200 | data = " ".join(data).replace("\n", ";") 201 | 202 | cell = np.fromstring( 203 | data, dtype=np.int32, sep=";") 204 | 205 | cell = np.reshape( 206 | cell, (lnum, vnum), order="C") 207 | 208 | cell = rfn.unstructured_to_structured( 209 | cell, dtype=jigsaw_msh_t.EDGE2_t, align=True) 210 | 211 | mesh.edge2 = cell 212 | 213 | return 214 | 215 | 216 | def loadtria3(mesh, fptr, ltag): 217 | """ 218 | LOADTRIA3: load the TRIA3 data segment from file. 219 | 220 | """ 221 | lnum = int(ltag[1]); vnum = 4 222 | 223 | data = [] 224 | for line in range(lnum): 225 | data.append(fptr.readline()) 226 | 227 | data = " ".join(data).replace("\n", ";") 228 | 229 | cell = np.fromstring( 230 | data, dtype=np.int32, sep=";") 231 | 232 | cell = np.reshape( 233 | cell, (lnum, vnum), order="C") 234 | 235 | cell = rfn.unstructured_to_structured( 236 | cell, dtype=jigsaw_msh_t.TRIA3_t, align=True) 237 | 238 | mesh.tria3 = cell 239 | 240 | return 241 | 242 | 243 | def loadquad4(mesh, fptr, ltag): 244 | """ 245 | LOADUAD4: load the QUAD4 data segment from file. 246 | 247 | """ 248 | lnum = int(ltag[1]); vnum = 5 249 | 250 | data = [] 251 | for line in range(lnum): 252 | data.append(fptr.readline()) 253 | 254 | data = " ".join(data).replace("\n", ";") 255 | 256 | cell = np.fromstring( 257 | data, dtype=np.int32, sep=";") 258 | 259 | cell = np.reshape( 260 | cell, (lnum, vnum), order="C") 261 | 262 | cell = rfn.unstructured_to_structured( 263 | cell, dtype=jigsaw_msh_t.QUAD4_t, align=True) 264 | 265 | mesh.quad4 = cell 266 | 267 | return 268 | 269 | 270 | def loadtria4(mesh, fptr, ltag): 271 | """ 272 | LOADTRIA4: load the TRIA4 data segment from file. 273 | 274 | """ 275 | lnum = int(ltag[1]); vnum = 5 276 | 277 | data = [] 278 | for line in range(lnum): 279 | data.append(fptr.readline()) 280 | 281 | data = " ".join(data).replace("\n", ";") 282 | 283 | cell = np.fromstring( 284 | data, dtype=np.int32, sep=";") 285 | 286 | cell = np.reshape( 287 | cell, (lnum, vnum), order="C") 288 | 289 | cell = rfn.unstructured_to_structured( 290 | cell, dtype=jigsaw_msh_t.TRIA4_t, align=True) 291 | 292 | mesh.tria4 = cell 293 | 294 | return 295 | 296 | 297 | def loadhexa8(mesh, fptr, ltag): 298 | """ 299 | LOADHEXA8: load the HEXA8 data segment from file. 300 | 301 | """ 302 | lnum = int(ltag[1]); vnum = 9 303 | 304 | data = [] 305 | for line in range(lnum): 306 | data.append(fptr.readline()) 307 | 308 | data = " ".join(data).replace("\n", ";") 309 | 310 | cell = np.fromstring( 311 | data, dtype=np.int32, sep=";") 312 | 313 | cell = np.reshape( 314 | cell, (lnum, vnum), order="C") 315 | 316 | cell = rfn.unstructured_to_structured( 317 | cell, dtype=jigsaw_msh_t.HEXA8_t, align=True) 318 | 319 | mesh.hexa8 = cell 320 | 321 | return 322 | 323 | 324 | def loadpyra5(mesh, fptr, ltag): 325 | """ 326 | LOADPYRA5: load the PYRA5 data segment from file. 327 | 328 | """ 329 | lnum = int(ltag[1]); vnum = 6 330 | 331 | data = [] 332 | for line in range(lnum): 333 | data.append(fptr.readline()) 334 | 335 | data = " ".join(data).replace("\n", ";") 336 | 337 | cell = np.fromstring( 338 | data, dtype=np.int32, sep=";") 339 | 340 | cell = np.reshape( 341 | cell, (lnum, vnum), order="C") 342 | 343 | cell = rfn.unstructured_to_structured( 344 | cell, dtype=jigsaw_msh_t.PYRA5_t, align=True) 345 | 346 | mesh.pyra5 = cell 347 | 348 | return 349 | 350 | 351 | def loadwedg6(mesh, fptr, ltag): 352 | """ 353 | LOADWEDG6: load the WEDG6 data segment from file. 354 | 355 | """ 356 | lnum = int(ltag[1]); vnum = 7 357 | 358 | data = [] 359 | for line in range(lnum): 360 | data.append(fptr.readline()) 361 | 362 | data = " ".join(data).replace("\n", ";") 363 | 364 | cell = np.fromstring( 365 | data, dtype=np.int32, sep=";") 366 | 367 | cell = np.reshape( 368 | cell, (lnum, vnum), order="C") 369 | 370 | cell = rfn.unstructured_to_structured( 371 | cell, dtype=jigsaw_msh_t.WEDG6_t, align=True) 372 | 373 | mesh.wedg6 = cell 374 | 375 | return 376 | 377 | 378 | def loadbound(mesh, fptr, ltag): 379 | """ 380 | LOADBOUND: load the BOUND data segment from file. 381 | 382 | """ 383 | lnum = int(ltag[1]); vnum = 3 384 | 385 | data = [] 386 | for line in range(lnum): 387 | data.append(fptr.readline()) 388 | 389 | data = " ".join(data).replace("\n", ";") 390 | 391 | bnds = np.fromstring( 392 | data, dtype=np.int32, sep=";") 393 | 394 | bnds = np.reshape( 395 | bnds, (lnum, vnum), order="C") 396 | 397 | bnds = rfn.unstructured_to_structured( 398 | bnds, dtype=jigsaw_msh_t.BOUND_t, align=True) 399 | 400 | mesh.bound = bnds 401 | 402 | return 403 | 404 | 405 | def loadcoord(mesh, fptr, ltag): 406 | """ 407 | LOADCOORD: load the COORD data segment from file. 408 | 409 | """ 410 | ctag = ltag[1].split(";") 411 | 412 | idim = int(ctag[0]) 413 | lnum = int(ctag[1]) 414 | 415 | mesh.ndims = max(mesh.ndims, idim) 416 | 417 | data = [] 418 | for line in range(lnum): 419 | data.append(fptr.readline()) 420 | 421 | data = " ".join(data).replace("\n", ";") 422 | 423 | vals = np.fromstring( 424 | data, dtype=np.float64, sep=";") 425 | 426 | if (idim == +1): 427 | mesh.xgrid = np.reshape( 428 | vals, (lnum, ), order="C") 429 | 430 | elif (idim == +2): 431 | mesh.ygrid = np.reshape( 432 | vals, (lnum, ), order="C") 433 | 434 | elif (idim == +3): 435 | mesh.zgrid = np.reshape( 436 | vals, (lnum, ), order="C") 437 | 438 | return 439 | 440 | 441 | def loadlines(mesh, fptr, line): 442 | """ 443 | LOADLINES: load the next non-null line from file. 444 | 445 | """ 446 | 447 | #------------------------------ skip any 'comment' lines 448 | 449 | if (line[0] != '#'): 450 | 451 | #------------------------------ split about '=' charact. 452 | ltag = line.split("=") 453 | kind = ltag[0].upper() 454 | 455 | if (kind == "MSHID"): 456 | 457 | #----------------------------------- parse MSHID struct. 458 | loadmshid(mesh, fptr, ltag) 459 | 460 | elif (kind == "NDIMS"): 461 | 462 | #----------------------------------- parse NDIMS struct. 463 | mesh.ndims = int(ltag[1]) 464 | 465 | elif (kind == "RADII"): 466 | 467 | #----------------------------------- parse RADII struct. 468 | loadradii(mesh, fptr, ltag) 469 | 470 | elif (kind == "POINT"): 471 | 472 | #----------------------------------- parse POINT struct. 473 | loadpoint(mesh, fptr, ltag) 474 | 475 | elif (kind == "SEEDS"): 476 | 477 | #----------------------------------- parse SEEDS struct. 478 | loadseeds(mesh, fptr, ltag) 479 | 480 | elif (kind == "POWER"): 481 | 482 | #----------------------------------- parse POWER struct. 483 | loadpower(mesh, fptr, ltag) 484 | 485 | elif (kind == "VALUE"): 486 | 487 | #----------------------------------- parse VALUE struct. 488 | loadvalue(mesh, fptr, ltag) 489 | 490 | elif (kind == "SLOPE"): 491 | 492 | #----------------------------------- parse SLOPE struct. 493 | loadslope(mesh, fptr, ltag) 494 | 495 | elif (kind == "EDGE2"): 496 | 497 | #----------------------------------- parse EDGE2 struct. 498 | loadedge2(mesh, fptr, ltag) 499 | 500 | elif (kind == "TRIA3"): 501 | 502 | #----------------------------------- parse TRIA3 struct. 503 | loadtria3(mesh, fptr, ltag) 504 | 505 | elif (kind == "QUAD4"): 506 | 507 | #----------------------------------- parse QUAD4 struct. 508 | loadquad4(mesh, fptr, ltag) 509 | 510 | elif (kind == "TRIA4"): 511 | 512 | #----------------------------------- parse TRIA4 struct. 513 | loadtria4(mesh, fptr, ltag) 514 | 515 | elif (kind == "HEXA8"): 516 | 517 | #----------------------------------- parse HEXA8 struct. 518 | loadhexa8(mesh, fptr, ltag) 519 | 520 | elif (kind == "PYRA5"): 521 | 522 | #----------------------------------- parse PYRA5 struct. 523 | loadpyra5(mesh, fptr, ltag) 524 | 525 | elif (kind == "WEDG6"): 526 | 527 | #----------------------------------- parse WEDG6 struct. 528 | loadwedg6(mesh, fptr, ltag) 529 | 530 | elif (kind == "BOUND"): 531 | 532 | #----------------------------------- parse BOUND struct. 533 | loadbound(mesh, fptr, ltag) 534 | 535 | elif (kind == "COORD"): 536 | 537 | #----------------------------------- parse COORD struct. 538 | loadcoord(mesh, fptr, ltag) 539 | 540 | return 541 | 542 | 543 | def sanitise_grid(mesh, data): 544 | """ 545 | SANITISE-GRID: reshape the "unrolled" array in DATA such 546 | that the matrix matches the dimensions of the structured 547 | grid objects. 548 | 549 | """ 550 | 551 | F = "F" # use fortran matrix ordering 552 | 553 | if (data is not None and data.size != +0): 554 | #--------------------------- finalise VALUE.shape layout 555 | if (mesh.ndims == +2): 556 | #--------------------------- reshape data to 2-dim array 557 | if (mesh.xgrid is None or 558 | mesh.xgrid.size == 0): 559 | raise Exception("Invalid XGRID") 560 | 561 | if (mesh.ygrid is None or 562 | mesh.ygrid.size == 0): 563 | raise Exception("Invalid YGRID") 564 | 565 | num1 = mesh.ygrid.size 566 | num2 = mesh.xgrid.size 567 | 568 | nprd = (num1 * num2) 569 | 570 | nval = mesh.value.size // nprd 571 | 572 | size = (num1, num2, nval) 573 | 574 | data = np.squeeze( 575 | np.reshape(data, size, order=F)) 576 | 577 | if (mesh.ndims == +3): 578 | #--------------------------- reshape data to 3-dim array 579 | if (mesh.xgrid is None or 580 | mesh.xgrid.size == 0): 581 | raise Exception("Invalid XGRID") 582 | 583 | if (mesh.ygrid is None or 584 | mesh.ygrid.size == 0): 585 | raise Exception("Invalid YGRID") 586 | 587 | if (mesh.zgrid is None or 588 | mesh.zgrid.size == 0): 589 | raise Exception("Invalid ZGRID") 590 | 591 | num1 = mesh.ygrid.size 592 | num2 = mesh.xgrid.size 593 | num3 = mesh.zgrid.size 594 | 595 | nprd = (num1 * num2 * num3) 596 | 597 | nval = mesh.value.size // nprd 598 | 599 | size = (num1, num2, num3, nval) 600 | 601 | data = np.squeeze( 602 | np.reshape(data, size, order=F)) 603 | 604 | return data 605 | 606 | 607 | def loadmsh(name, mesh): 608 | """ 609 | LOADMSH: load a JIGSAW MSH obj. from file. 610 | 611 | LOADMSH(NAME, MESH) 612 | 613 | MESH is JIGSAW's primary mesh/grid/geom class. See MSH_t 614 | for details. 615 | 616 | Data in MESH is loaded on-demand -- any objects included 617 | in the file will be read. 618 | 619 | """ 620 | 621 | if (not isinstance(name, str)): 622 | raise Exception("Incorrect type: NAME.") 623 | 624 | if (not isinstance(mesh, jigsaw_msh_t)): 625 | raise Exception("Incorrect type: MESH.") 626 | 627 | with Path(name).open("r") as fptr: 628 | while (True): 629 | 630 | #--------------------------- get the next line from file 631 | line = fptr.readline() 632 | 633 | if (len(line) != +0): 634 | 635 | #--------------------------- parse next non-null section 636 | loadlines(mesh, fptr, line) 637 | 638 | else: 639 | #--------------------------- reached end-of-file: done!! 640 | break 641 | 642 | if (mesh.mshID.lower() in 643 | ["euclidean-grid", "ellipsoid-grid"]): 644 | 645 | mesh.value = \ 646 | sanitise_grid(mesh, mesh.value) 647 | 648 | mesh.slope = \ 649 | sanitise_grid(mesh, mesh.slope) 650 | 651 | return 652 | -------------------------------------------------------------------------------- /msh/msh_t.py: -------------------------------------------------------------------------------- 1 | 2 | #-- snipped from: github.com/dengwirda/jigsaw-python 3 | 4 | """ MSH_T: Container struct. for JIGSAW's mesh data. 5 | 6 | .IF. MESH.MSHID == 'EUCLIDEAN-MESH': 7 | ----------------------------------- 8 | 9 | MESH.VERT2 - [V2x 1] array of 2dim. point coordinates, 10 | VERT2 is a JIGSAW_MSH_t.VERT2_t structure, where: 11 | VERT2["COORD"] is an array of coordinate values, 12 | VERT2["IDTAG"] is an ID tag assigned at each vertex. 13 | 14 | MESH.VERT3 - [V3x 1] array of 3dim. point coordinates, 15 | VERT3 is a JIGSAW_MSH_t.VERT3_t structure, where: 16 | VERT3["COORD"] is an array of coordinate values, 17 | VERT3["IDTAG"] is an ID tag assigned at each vertex. 18 | 19 | MESH.POWER - [NVx 1] array of vertex "weights" 20 | associated with the dual "power" tessellation. 21 | 22 | MESH.EDGE2 - [E2x 1] array of EDGE-2 cell definitions, 23 | EDGE2 is a JIGSAW_MSH_t.EDGE2_t structure, where: 24 | EDGE2["INDEX"] is an array of cell indexing, 25 | EDGE2["IDTAG"] is an ID tag assigned at each cell. 26 | 27 | MESH.TRIA3 - [T3x 1] array of TRIA-3 cell definitions, 28 | TRIA3 is a JIGSAW_MSH_t.TRIA3_t structure, where: 29 | TRIA3["INDEX"] is an array of cell indexing, 30 | TRIA3["IDTAG"] is an ID tag assigned at each cell. 31 | 32 | MESH.QUAD4 - [Q4x 1] array of QUAD-4 cell definitions, 33 | QUAD4 is a JIGSAW_MSH_t.QUAD4_t structure, where: 34 | QUAD4["INDEX"] is an array of cell indexing, 35 | QUAD4["IDTAG"] is an ID tag assigned at each cell. 36 | 37 | MESH.TRIA4 - [T4x 1] array of TRIA-4 cell definitions, 38 | TRIA4 is a JIGSAW_MSH_t.QUAD4_t structure, where: 39 | TRIA4["INDEX"] is an array of cell indexing, 40 | TRIA4["IDTAG"] is an ID tag assigned at each cell. 41 | 42 | MESH.HEXA8 - [H8x 1] array of HEXA-8 cell definitions, 43 | HEXA8 is a JIGSAW_MSH_t.HEXA8_t structure, where: 44 | HEXA8["INDEX"] is an array of cell indexing, 45 | HEXA8["IDTAG"] is an ID tag assigned at each cell. 46 | 47 | MESH.WEDG6 - [W6x 1] array of WEDG-6 cell definitions, 48 | WEDG6 is a JIGSAW_MSH_t.WEDG6_t structure, where: 49 | WEDG6["INDEX"] is an array of cell indexing, 50 | WEDG6["IDTAG"] is an ID tag assigned at each cell. 51 | 52 | MESH.PYRA5 - [P5x 1] array of PYRA-5 cell definitions, 53 | PYRA5 is a JIGSAW_MSH_t.PYRA5_t structure, where: 54 | PYRA5["INDEX"] is an array of cell indexing, 55 | PYRA5["IDTAG"] is an ID tag assigned at each cell. 56 | 57 | MESH.BOUND - [NBx 1] array of "boundary" indexing, 58 | BOUND is a JIGSAW_MSH_t.BOUND_t structure, defining 59 | how elements in the geometry are associated with 60 | enclosed areas/volumes, herein known as "parts": 61 | BOUND["INDEX"] is an array of cell indexing, 62 | BOUND["CELLS"] is an array of cell tags, defining 63 | which element "kind" is indexed via BOUND["INDEX"] 64 | BOUND["IDTAG"] is an ID tag assigned to each part. 65 | 66 | In the default case, where BOUND is not specified, 67 | all elements in the geometry are assumed to define 68 | the boundaries of enclosed "parts". 69 | 70 | MESH.VALUE - [NPxNV] array of "values" associated with 71 | the vertices of the mesh. 72 | 73 | .IF. MESH.MSHID == 'ELLIPSOID-MESH': 74 | ----------------------------------- 75 | 76 | MESH.RADII - [ 3x 1] array of principal ellipsoid radii. 77 | 78 | Additionally, entities described in the 'EUCLIDEAN-MESH' 79 | specification may be optionally defined. 80 | 81 | .IF. MESH.MSHID == 'EUCLIDEAN-GRID': 82 | .OR. MESH.MSHID == 'ELLIPSOID-GRID': 83 | ----------------------------------- 84 | 85 | MESH.XGRID - [NXx 1] array of "x-axis" grid coordinates. 86 | Values must increase or decrease monotonically. 87 | 88 | MESH.YGRID - [NYx 1] array of "y-axis" grid coordinates. 89 | Values must increase or decrease monotonically. 90 | 91 | MESH.ZGRID - [NZx 1] array of "z-axis" grid coordinates. 92 | Values must increase or decrease monotonically. 93 | 94 | MESH.VALUE - [NMxNV] array of "values" associated with 95 | vertices in the grid, where NM is the product of 96 | the dimensions of the grid. NV values are associated 97 | with each vertex. 98 | 99 | MESH.SLOPE - [NMx 1] array of "slopes" associated with 100 | vertices in the grid, where NM is the product of 101 | the dimensions of the grid. Slope values define the 102 | gradient-limits ||dh/dx|| used by the Eikonal solver 103 | MARCHE. 104 | 105 | See also JIG_t 106 | 107 | -------------------------------------------------------- 108 | """ 109 | 110 | import numpy as np 111 | 112 | 113 | class jigsaw_msh_t: 114 | #------------------------------------------ MESH typedef 115 | REALS_t = np.float64 116 | INDEX_t = np.int32 117 | 118 | T = True 119 | 120 | VERT2_t = np.dtype([("coord", REALS_t, 2), 121 | ("IDtag", INDEX_t)], align=T) 122 | 123 | VERT3_t = np.dtype([("coord", REALS_t, 3), 124 | ("IDtag", INDEX_t)], align=T) 125 | 126 | EDGE2_t = np.dtype([("index", INDEX_t, 2), 127 | ("IDtag", INDEX_t)], align=T) 128 | 129 | TRIA3_t = np.dtype([("index", INDEX_t, 3), 130 | ("IDtag", INDEX_t)], align=T) 131 | 132 | QUAD4_t = np.dtype([("index", INDEX_t, 4), 133 | ("IDtag", INDEX_t)], align=T) 134 | 135 | TRIA4_t = np.dtype([("index", INDEX_t, 4), 136 | ("IDtag", INDEX_t)], align=T) 137 | 138 | HEXA8_t = np.dtype([("index", INDEX_t, 8), 139 | ("IDtag", INDEX_t)], align=T) 140 | 141 | WEDG6_t = np.dtype([("index", INDEX_t, 6), 142 | ("IDtag", INDEX_t)], align=T) 143 | 144 | PYRA5_t = np.dtype([("index", INDEX_t, 5), 145 | ("IDtag", INDEX_t)], align=T) 146 | 147 | BOUND_t = np.dtype([("IDtag", INDEX_t), 148 | ("index", INDEX_t), 149 | ("cells", INDEX_t)], align=T) 150 | 151 | def __init__(self): 152 | #------------------------------------------ MESH struct. 153 | self.mshID = "euclidean-mesh" 154 | self.ndims = +0 155 | 156 | DIM1 = (0); DIM2 = (0, 0) 157 | 158 | self.radii = np.empty( 159 | DIM1, dtype=jigsaw_msh_t.REALS_t) 160 | 161 | self.vert2 = np.empty( 162 | DIM1, dtype=jigsaw_msh_t.VERT2_t) 163 | self.vert3 = np.empty( 164 | DIM1, dtype=jigsaw_msh_t.VERT3_t) 165 | 166 | self.seed2 = np.empty( 167 | DIM1, dtype=jigsaw_msh_t.VERT2_t) 168 | self.seed3 = np.empty( 169 | DIM1, dtype=jigsaw_msh_t.VERT3_t) 170 | 171 | self.power = np.empty( 172 | DIM2, dtype=jigsaw_msh_t.REALS_t) 173 | 174 | self.edge2 = np.empty( 175 | DIM1, dtype=jigsaw_msh_t.EDGE2_t) 176 | self.tria3 = np.empty( 177 | DIM1, dtype=jigsaw_msh_t.TRIA3_t) 178 | self.quad4 = np.empty( 179 | DIM1, dtype=jigsaw_msh_t.QUAD4_t) 180 | self.tria4 = np.empty( 181 | DIM1, dtype=jigsaw_msh_t.TRIA4_t) 182 | self.hexa8 = np.empty( 183 | DIM1, dtype=jigsaw_msh_t.HEXA8_t) 184 | self.pyra5 = np.empty( 185 | DIM1, dtype=jigsaw_msh_t.PYRA5_t) 186 | self.wedg6 = np.empty( 187 | DIM1, dtype=jigsaw_msh_t.WEDG6_t) 188 | 189 | self.bound = np.empty( 190 | DIM1, dtype=jigsaw_msh_t.BOUND_t) 191 | 192 | self.xgrid = np.empty( 193 | DIM1, dtype=jigsaw_msh_t.REALS_t) 194 | self.ygrid = np.empty( 195 | DIM1, dtype=jigsaw_msh_t.REALS_t) 196 | self.zgrid = np.empty( 197 | DIM1, dtype=jigsaw_msh_t.REALS_t) 198 | 199 | self.value = np.empty( 200 | DIM2, dtype=jigsaw_msh_t.REALS_t) 201 | 202 | self.slope = np.empty( 203 | DIM2, dtype=jigsaw_msh_t.REALS_t) 204 | 205 | @property 206 | def point(self): 207 | #------------------------------------------ POINT helper 208 | verts = self.vert2 209 | if (verts is not None and verts.size): 210 | return verts 211 | 212 | verts = self.vert3 213 | if (verts is not None and verts.size): 214 | return verts 215 | 216 | return None 217 | 218 | @point.setter 219 | def point(self, verts): 220 | #------------------------------------------ POINT helper 221 | if (isinstance(verts, np.ndarray) and 222 | verts.dtype == self.VERT2_t): 223 | 224 | self.vert2 = verts; return 225 | 226 | if (isinstance(verts, np.ndarray) and 227 | verts.dtype == self.VERT3_t): 228 | 229 | self.vert3 = verts; return 230 | 231 | raise Exception("Invalid POINT type") 232 | 233 | @property 234 | def seeds(self): 235 | #------------------------------------------ SEEDS helper 236 | seeds = self.seed2 237 | if (seeds is not None and seeds.size): 238 | return seeds 239 | 240 | seeds = self.seed3 241 | if (seeds is not None and seeds.size): 242 | return seeds 243 | 244 | return None 245 | 246 | @seeds.setter 247 | def seeds(self, seeds): 248 | #------------------------------------------ SEEDS helper 249 | if (isinstance(seeds, np.ndarray) and 250 | seeds.dtype == self.VERT2_t): 251 | 252 | self.seed2 = seeds; return 253 | 254 | if (isinstance(seeds, np.ndarray) and 255 | seeds.dtype == self.VERT3_t): 256 | 257 | self.seed3 = seeds; return 258 | 259 | raise Exception("Invalid SEEDS type") 260 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | cython 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = W503,W504,E115,E265,E271,E701,E702,E704,F401 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import io 3 | import os 4 | from setuptools import setup, find_packages 5 | from setuptools.extension import Extension 6 | import numpy as np 7 | 8 | # https://stackoverflow.com/questions/4505747/ 9 | # how-should-i-structure-a-python-package-that-contains-cython-code 10 | 11 | # https://stackoverflow.com/questions/14657375/ 12 | # cython-fatal-error-numpy-arrayobject-h-no-such-file-or-directory 13 | 14 | # https://github.com/cython/cython/issues/1480 15 | 16 | # https://stackoverflow.com/questions/14372706/ 17 | # visual-studio-cant-build-due-to-rc-exe 18 | 19 | EXT_MODULES = [] 20 | 21 | try: 22 | from Cython.Build import cythonize 23 | import Cython.Compiler.Options 24 | 25 | Cython.Compiler.Options.annotate = True 26 | 27 | EXT_MODULES += cythonize(Extension( 28 | "inpoly.inpoly_", 29 | sources=[os.path.join("inpoly", "inpoly_.pyx")], 30 | include_dirs=[np.get_include()]), 31 | annotate=True 32 | ) 33 | 34 | except ImportError: 35 | EXT_MODULES += [Extension( 36 | "inpoly.inpoly_", 37 | sources=[os.path.join("inpoly", "inpoly_.c")], 38 | include_dirs=[np.get_include()]) 39 | ] 40 | 41 | NAME = "inpoly" 42 | DESCRIPTION = "Fast point(s)-in-polygon queries." 43 | AUTHOR = "Darren Engwirda and Keith Roberts" 44 | AUTHOR_EMAIL = "d.engwirda@gmail.com" 45 | URL = "https://github.com/dengwirda/inpoly-python" 46 | VERSION = "0.2.2" 47 | REQUIRES_PYTHON = ">=3.3.0" 48 | KEYWORDS = "Point-in-Polygon Geometry GIS" 49 | 50 | REQUIRED = [ 51 | "numpy" 52 | ] 53 | 54 | CLASSIFY = [ 55 | "Development Status :: 4 - Beta", 56 | "Operating System :: OS Independent", 57 | "Intended Audience :: Science/Research", 58 | "Programming Language :: Python", 59 | "Programming Language :: Python :: 3", 60 | "Topic :: Scientific/Engineering", 61 | "Topic :: Scientific/Engineering :: Mathematics", 62 | "Topic :: Scientific/Engineering :: Physics", 63 | "Topic :: Scientific/Engineering :: Visualization", 64 | "Topic :: Scientific/Engineering :: GIS" 65 | ] 66 | 67 | HERE = os.path.abspath(os.path.dirname(__file__)) 68 | 69 | try: 70 | with io.open(os.path.join( 71 | HERE, "README.md"), encoding="utf-8") as f: 72 | LONG_DESCRIPTION = "\n" + f.read() 73 | 74 | except FileNotFoundError: 75 | LONG_DESCRIPTION = DESCRIPTION 76 | 77 | setup( 78 | name=NAME, 79 | version=VERSION, 80 | description=DESCRIPTION, 81 | long_description=LONG_DESCRIPTION, 82 | long_description_content_type="text/markdown", 83 | license="custom", 84 | author=AUTHOR, 85 | author_email=AUTHOR_EMAIL, 86 | python_requires=REQUIRES_PYTHON, 87 | keywords=KEYWORDS, 88 | url=URL, 89 | packages=find_packages(exclude=["msh", ]), # just inpoly 90 | ext_modules=EXT_MODULES, 91 | install_requires=REQUIRED, 92 | classifiers=CLASSIFY 93 | ) 94 | --------------------------------------------------------------------------------