├── .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 |
--------------------------------------------------------------------------------