PageRenderTime 56ms CodeModel.GetById 10ms app.highlight 41ms RepoModel.GetById 1ms app.codeStats 0ms

/layers-store/src/main/java/org/ala/layers/intersect/ComplexRegion.java

http://alageospatialportal.googlecode.com/
Java | 637 lines | 412 code | 68 blank | 157 comment | 197 complexity | f42f680534029812dd6003067c7f80bf MD5 | raw file
  1/**************************************************************************
  2 *  Copyright (C) 2010 Atlas of Living Australia
  3 *  All Rights Reserved.
  4 *
  5 *  The contents of this file are subject to the Mozilla Public
  6 *  License Version 1.1 (the "License"); you may not use this file
  7 *  except in compliance with the License. You may obtain a copy of
  8 *  the License at http://www.mozilla.org/MPL/
  9 *
 10 *  Software distributed under the License is distributed on an "AS
 11 *  IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 12 *  implied. See the License for the specific language governing
 13 *  rights and limitations under the License.
 14 ***************************************************************************/
 15
 16package org.ala.layers.intersect;
 17
 18import java.util.ArrayList;
 19
 20/**
 21 * ComplexRegion is a collection of SimpleRegion, expect POLYGONs for now.
 22 * 
 23 * treat as a shape file, overlapping regions cancel out presence.
 24 * 
 25 * TODO: clockwise/anticlockwise identification
 26 * 
 27 * @author Adam Collins
 28 */
 29public class ComplexRegion extends SimpleRegion {
 30
 31    static public SimpleRegion parseComplexRegion(String[] polygons) {
 32        ComplexRegion cr = new ComplexRegion();
 33
 34        for (String s : polygons) {
 35            cr.addPolygon(SimpleRegion.parseSimpleRegion(s));
 36        }
 37
 38        cr.useMask(-1, -1, -1);
 39
 40        return cr;
 41    }
 42    /**
 43     * list of SimpleRegion members
 44     */
 45    ArrayList<SimpleRegion> simpleregions;
 46    /**
 47     * bounding box for all, see SimpleRegion boundingbox.
 48     */
 49    double[][] boundingbox_all;
 50    /**
 51     * value assigned
 52     */
 53    int value;
 54    /**
 55     * array for speeding up isWithin
 56     */
 57    byte[][] mask;
 58    Object[][] maskDepth;
 59    /**
 60     * mask height
 61     */
 62    int mask_height;
 63    /**
 64     * mask width
 65     */
 66    int mask_width;
 67    /**
 68     * mask multiplier for longitude inputs
 69     */
 70    double mask_long_multiplier;
 71    /**
 72     * mask mulitplier for latitude inputs
 73     */
 74    double mask_lat_multiplier;
 75    /**
 76     * maintain mapping for simpleregions belonging to the same polygon
 77     */
 78    ArrayList<Integer> polygons;
 79
 80    /**
 81     * Constructor for empty ComplexRegion
 82     */
 83    public ComplexRegion() {
 84        super();
 85
 86        simpleregions = new ArrayList();
 87        boundingbox_all = new double[2][2];
 88        value = -1;
 89        mask = null;
 90        polygons = new ArrayList();
 91    }
 92
 93    /**
 94     * sets integer value stored
 95     * @param value_ as int
 96     */
 97    public void setValue(int value_) {
 98        value = value_;
 99    }
100
101    /**
102     * gets integer value stored
103     * @return int
104     */
105    public int getValue() {
106        return value;
107    }
108
109    /**
110     * gets the bounding box for shapes in this ComplexRegion
111     *
112     * @return bounding box for ComplexRegion as double [][]
113     */
114    @Override
115    public double[][] getBoundingBox() {
116        return boundingbox_all;
117    }
118
119    /**
120     * adds a new polygon
121     *
122     * note: if a mask is in use must call <code>useMask</code> again
123     * @param points_ points = double[n][2]
124     * where
125     * 	n is number of points
126     *  [][0] is longitude
127     *  [][1] is latitude
128     */
129    public void addPolygon(SimpleRegion sr) {
130        simpleregions.add(sr);
131
132        /* update boundingbox_all */
133        double[][] bb = sr.getBoundingBox();
134        if (simpleregions.size() == 1 || boundingbox_all[0][0] > bb[0][0]) {
135            boundingbox_all[0][0] = bb[0][0];
136        }
137        if (simpleregions.size() == 1 || boundingbox_all[1][0] < bb[1][0]) {
138            boundingbox_all[1][0] = bb[1][0];
139        }
140        if (simpleregions.size() == 1 || boundingbox_all[0][1] > bb[0][1]) {
141            boundingbox_all[0][1] = bb[0][1];
142        }
143        if (simpleregions.size() == 1 || boundingbox_all[1][1] < bb[1][1]) {
144            boundingbox_all[1][1] = bb[1][1];
145        }
146
147        bounding_box = boundingbox_all;
148    }
149
150    /**
151     * returns true when the point provided is within the ComplexRegion
152     *
153     * uses <code>mask</code> when available
154     *
155     * note: type UNDEFINED implies no boundary, always returns true.
156     *
157     * @param longitude
158     * @param latitude
159     * @return true iff point is within or on the edge of this ComplexRegion
160     */
161    @Override
162    public boolean isWithin(double longitude, double latitude) {
163        if (simpleregions.size() == 1) {
164            return simpleregions.get(0).isWithin(longitude, latitude);
165        }
166        if (boundingbox_all[0][0] > longitude || boundingbox_all[1][0] < longitude
167                || boundingbox_all[0][1] > latitude || boundingbox_all[1][1] < latitude) {
168            return false;
169        }
170
171        short[] countsIn = new short[polygons.get(polygons.size()-1) + 1];
172        //int count_in = 0;       //count of regions overlapping the point
173        if (mask != null) {
174            /* use mask if exists */
175            int long1 = (int) Math.floor((longitude - boundingbox_all[0][0]) * mask_long_multiplier);
176            int lat1 = (int) Math.floor((latitude - boundingbox_all[0][1]) * mask_lat_multiplier);
177
178            if (long1 == mask[0].length) {
179                long1--;
180            }
181            if (lat1 == mask.length) {
182                lat1--;
183            }
184
185            if (mask[lat1][long1] == SimpleRegion.GI_FULLY_PRESENT) {
186                return true;
187            } else if (mask[lat1][long1] == SimpleRegion.GI_UNDEFINED
188                    || mask[lat1][long1] == SimpleRegion.GI_ABSENCE) {
189                return false;
190            }
191            //partial, try maskDepth and sum overlaps
192            if (maskDepth != null && maskDepth[lat1][long1] != null) {
193                int[] d = (int[]) maskDepth[lat1][long1];
194                for (int i = 0; i < d.length; i++) {
195                    if (simpleregions.get(d[i]).isWithin(longitude, latitude)) {
196                        countsIn[polygons.get(d[i])]++;
197                    }
198                }
199                /* true iif within an odd number of regions for any polygon*/
200                for (int i = 0; i < countsIn.length; i++) {
201                    if (countsIn[i] % 2 == 1) {
202                        return true;
203                    }
204                }
205            }
206            
207        }
208
209        /* check for all SimpleRegions */
210        for (int i = 0; i < simpleregions.size(); i++) {
211            if (simpleregions.get(i).isWithin(longitude, latitude)) {
212                countsIn[polygons.get(i)]++;
213            }
214        }
215
216        /* true iif within an odd number of regions for any polygon*/
217        for (int i = 0; i < countsIn.length; i++) {
218            if (countsIn[i] % 2 == 1) {
219                return true;
220            }
221        }
222        return false;
223    }
224
225    /**
226     * builds a grid (mask) to speed up isWithin.
227     *
228     * TODO: split out shapes with large numbers of points in GI_PARTIALLY_PRESENT grid cells.
229     *
230     * TODO: automatic(best) height/width specification
231     *
232     * @param width
233     * @param height
234     */
235    public void useMask(int width, int height, int depthThreashold) {
236        //calculate defaults for -1 inputs
237        double[][] bb = getBoundingBox();
238        int length = 0;
239        for (SimpleRegion sr : simpleregions) {
240            length += sr.getNumberOfPoints();
241        }
242        int w = (int) ((bb[1][0] - bb[0][0]) * 3);
243        int h = (int) ((bb[1][1] - bb[0][1]) * 3);
244        if (length > 5000) {
245            w = 200;
246            h = 200;
247        }
248        if (w > 200) {
249            w = 200;
250        }
251        if (h > 200) {
252            h = 200;
253        }
254        if (width == -1) {
255            width = w;
256        }
257        if (height == -1) {
258            height = h;
259        }
260        if (depthThreashold == -1) {
261            depthThreashold = 100;
262        }
263        if(width < 3 || height < 3) {
264            return;
265        }
266
267        int i, j;
268
269        /* class variables assignment */
270        mask_width = width;
271        mask_height = height;
272        mask_long_multiplier =
273                mask_width / (double) (boundingbox_all[1][0] - boundingbox_all[0][0]);
274        mask_lat_multiplier =
275                mask_height / (double) (boundingbox_all[1][1] - boundingbox_all[0][1]);
276
277        /* end result mask */
278        mask = new byte[height][width];
279        ArrayList<Integer>[][] md = null;
280        if (simpleregions.size() > depthThreashold) {
281            //use mask depth as well
282            md = new ArrayList[height][width];
283        }
284
285        /* temp mask for current Polygon */
286        byte[][] shapemask = new byte[height][width];
287
288        /* temp mask for current SimpleRegion */
289        byte[][] shapemaskregion = new byte[height][width];
290
291        int k = 0;
292        while(k < simpleregions.size()) {
293            int p = k;
294            for (; k < simpleregions.size()
295                    && (p == k || polygons.get(k - 1) == polygons.get(k)); k++) {
296
297                SimpleRegion sr = simpleregions.get(k);
298                sr.getOverlapGridCells(boundingbox_all[0][0], boundingbox_all[0][1], boundingbox_all[1][0], boundingbox_all[1][1], width, height, shapemaskregion);
299
300                //shapemaskregion into shapemask
301                for (i = 0; i < height; i++) {
302                    for (j = 0; j < width; j++) {
303                        if (shapemaskregion[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT
304                                || shapemask[i][j] ==SimpleRegion.GI_PARTIALLY_PRESENT) {
305                            shapemask[i][j] = SimpleRegion.GI_PARTIALLY_PRESENT;				//partially inside
306                            if (md != null) {
307                                if (md[i][j] == null) {
308                                    md[i][j] = new ArrayList<Integer>();
309                                }
310                                md[i][j].add(k);
311                            }
312                        } else if (shapemaskregion[i][j] == SimpleRegion.GI_FULLY_PRESENT ) {
313                            if (shapemask[i][j] == SimpleRegion.GI_FULLY_PRESENT ) {
314                                shapemask[i][j] = SimpleRegion.GI_ABSENCE;      //completely inside
315                            } else {
316                                shapemask[i][j] = SimpleRegion.GI_FULLY_PRESENT ;//completely outside (inside of a cutout region)
317                            }
318                            if (md != null) {
319                                if (md[i][j] == null) {
320                                    md[i][j] = new ArrayList<Integer>();
321                                }
322                                md[i][j].add(k);
323                            }
324                        }
325
326                        /* reset shapemaskregion for next part */
327                        shapemaskregion[i][j] = 0;
328                    }
329                }
330            }
331
332            //shapemask into mask
333            for (i = 0; i < height; i++) {
334                for (j = 0; j < width; j++) {
335                    if (shapemask[i][j] == SimpleRegion.GI_FULLY_PRESENT
336                            || mask[i][j] == SimpleRegion.GI_FULLY_PRESENT) {
337                        mask[i][j] = SimpleRegion.GI_FULLY_PRESENT;				//partially inside
338                    } else if (shapemask[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT) {
339                        mask[i][j] = SimpleRegion.GI_PARTIALLY_PRESENT;
340                    }
341
342                    /* reset shapemask for next part */
343                    shapemask[i][j] = 0;
344                }
345            }
346        }
347
348        //maskDepth to int[]
349        if (md != null) {
350            maskDepth = new Object[md.length][md[0].length];
351            for (i = 0; i < height; i++) {
352                for (j = 0; j < width; j++) {
353                    if (md[i][j] != null && mask[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT ) {
354                        int[] d = new int[md[i][j].size()];
355                        for (k = 0; k < d.length; k++) {
356                            d[k] = md[i][j].get(k);
357                        }
358                        maskDepth[i][j] = d;
359                    }
360                }
361            }
362        }
363    }
364
365    /**
366     * determines overlap with a grid
367     *
368     * for type POLYGON
369     * when <code>three_state_map</code> is not null populate it with one of:
370     * 	GI_UNDEFINED
371     * 	GI_PARTIALLY_PRESENT
372     * 	GI_FULLY_PRESENT
373     * 	GI_ABSENCE
374     *
375     * @param longitude1
376     * @param latitude1
377     * @param longitude2
378     * @param latitude2
379     * @param xres number of longitude segements as int
380     * @param yres number of latitude segments as int
381     * @return (x,y) as double [][2] for each grid cell at least partially falling
382     * within the specified region of the specified resolution beginning at 0,0
383     * for minimum longitude and latitude through to xres,yres for maximums     *
384     */
385    @Override
386    public int[][] getOverlapGridCells(double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
387        int i,j;
388        // if no threestate map exists, create one
389        if (three_state_map == null) {
390            three_state_map = new byte[height][width];
391        }
392
393        byte[][] tmpMask = new byte[height][width];
394
395        int k = 0;
396        while(k < simpleregions.size()) {
397            int p = k;
398            for (; k < simpleregions.size()
399                    && (p == k || polygons.get(k - 1).equals(polygons.get(k))); k++) {
400
401                SimpleRegion sr = simpleregions.get(k);
402                sr.getOverlapGridCells_Acc(longitude1, latitude1, longitude2, latitude2, width, height, tmpMask);
403            }
404
405            fillAccMask(p, k-1, longitude1, latitude1, longitude2, latitude2, width, height, tmpMask, true);
406
407            //tmpMask into mask
408            for (i = 0; i < height; i++) {
409                for (j = 0; j < width; j++) {
410                    if(tmpMask[i][j] == 2 || three_state_map[i][j] == 2) {
411                        three_state_map[i][j] = 2;
412                    } else if(tmpMask[i][j] == 1) {
413                        three_state_map[i][j] = 1;
414                    }
415
416                    /* reset shapemask for next part */
417                    tmpMask[i][j] = 0;
418                }
419            }
420        }
421
422        boolean cellsReturned = !noCellsReturned;
423        if (cellsReturned) {
424            int [][] data = new int[width * height][2];
425            int p = 0;
426            for(i=0;i<height;i++) {
427                for(j=0;j<width;j++) {
428                    if (three_state_map[i][j] != GI_UNDEFINED) {   //undefined == absence
429                        data[p][0] = j;
430                        data[p][1] = i;
431                        p++;
432                    }
433                }
434            }
435            data = java.util.Arrays.copyOf(data, p);
436        }
437
438
439        return null;
440    }
441
442    @Override
443    public boolean isWithin_EPSG900913(double longitude, double latitude) {
444        short[] countsIn = new short[polygons.get(polygons.size()-1) + 1];
445         /* check for all SimpleRegions */
446        for (int i = 0; i < simpleregions.size(); i++) {
447            if (simpleregions.get(i).isWithin_EPSG900913(longitude, latitude)) {
448                countsIn[polygons.get(i)]++;
449            }
450        }
451
452        /* true iif within an odd number of regions for any polygon*/
453        for (int i = 0; i < countsIn.length; i++) {
454            if (countsIn[i] % 2 == 1) {
455                return true;
456            }
457        }
458
459        return false;
460    }
461
462    @Override
463    public int[][] getOverlapGridCells_EPSG900913(double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
464        int i, j;
465
466        int[][] output = null;
467        byte[][] mask = three_state_map;
468
469        // if no threestate map exists, create one
470        if (mask == null) {
471            mask = new byte[height][width];
472            three_state_map = mask;
473        }
474
475        byte[][] tmpMask = new byte[height][width];
476
477        int k = 0;
478        while(k < simpleregions.size()) {
479            int p = k;
480            for (; k < simpleregions.size()
481                    && (p == k || polygons.get(k - 1).equals(polygons.get(k))); k++) {
482
483                SimpleRegion sr = simpleregions.get(k);
484                sr.getOverlapGridCells_Acc_EPSG900913(longitude1, latitude1, longitude2, latitude2, width, height, tmpMask);
485            }
486
487            fillAccMask_EPSG900913(k, p-1, longitude1, latitude1, longitude2, latitude2, width, height, tmpMask, true);
488
489            //tmpMask into mask
490            for (i = 0; i < height; i++) {
491                for (j = 0; j < width; j++) {
492                    if(tmpMask[i][j] == 2 || mask[i][j] == 2) {
493                        mask[i][j] = 2;
494                    } else if(tmpMask[i][j] == 1) {
495                        mask[i][j] = 1;
496                    }
497
498                    /* reset shapemask for next part */
499                    tmpMask[i][j] = 0;
500                }
501            }
502        }
503
504        boolean cellsReturned = !noCellsReturned;
505        if (cellsReturned) {
506            int [][] data = new int[width * height][2];
507            int p = 0;
508            for(i=0;i<height;i++) {
509                for(j=0;j<width;j++) {
510                    if (mask[i][j] != GI_UNDEFINED) {   //undefined == absence
511                        data[p][0] = j;
512                        data[p][1] = i;
513                        p++;
514                    }
515                }
516            }
517            data = java.util.Arrays.copyOf(data, p);
518            return data;
519        }
520
521        return null;
522    }
523
524    void addSet(ArrayList<SimpleRegion> simpleRegions) {
525        int nextSetNumber = (polygons.size() > 0) ? polygons.get(polygons.size() - 1) + 1 : 0;
526
527        for (int i = 0; i < simpleRegions.size(); i++) {
528            addPolygon(simpleRegions.get(i));
529            polygons.add(nextSetNumber);
530        }
531    }
532
533    public int[][] fillAccMask(int startPolygon, int endPolygon, double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
534        double divx = (longitude2 - longitude1) / width;
535        double divy = (latitude2 - latitude1) / height;
536
537        int i, j;
538        //do raster check
539        int[][] data = null;
540        boolean cellsReturned = !noCellsReturned;
541        if (cellsReturned) {
542            data = new int[width * height][2];
543        }
544        int p = 0;
545        for (j = 0; j < three_state_map[0].length; j++) {
546            for (i = 0; i < three_state_map.length; i++) {
547                if (three_state_map[i][j] == GI_PARTIALLY_PRESENT) {
548                    //if it is partially present, do nothing
549                } else if ((j == 0 || three_state_map[i][j - 1] == GI_PARTIALLY_PRESENT)) {
550                    if (i > 0
551                            && (three_state_map[i - 1][j] == GI_FULLY_PRESENT
552                            || three_state_map[i - 1][j] == GI_ABSENCE)) {
553                        //use same as LHS
554                        three_state_map[i][j] = three_state_map[i - 1][j];
555                    } else {
556                        int count = 0;
557                        for(int k=startPolygon;k<=endPolygon;k++) {
558                            count += isWithin(j * divx + divx / 2 + longitude1, i * divy + divy / 2 + latitude1)?1:0;
559                        }
560                        if(count % 2 == 1) {
561                            //if the previous was partially present, test
562                            three_state_map[i][j] = GI_FULLY_PRESENT;
563                        } //else absent
564                    } //else absent
565                } else {
566                    //if the previous was fully present, repeat
567                    //if the previous was absent, repeat
568                    three_state_map[i][j] = three_state_map[i][j - 1];
569                }
570
571                //apply to cells;
572                if (cellsReturned && three_state_map[i][j] != GI_UNDEFINED) {   //undefined == absence
573                    data[p][0] = j;
574                    data[p][1] = i;
575                    p++;
576                }
577            }
578        }
579        if (data != null) {
580            data = java.util.Arrays.copyOf(data, p);
581        }
582        return data;
583    }
584
585    public int[][] fillAccMask_EPSG900913(int startPolygon, int endPolygon, double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
586        double divx = (longitude2 - longitude1) / width;
587        double divy = (latitude2 - latitude1) / height;
588
589        int i, j;
590        //do raster check
591        int[][] data = null;
592        boolean cellsReturned = !noCellsReturned;
593        if (cellsReturned) {
594            data = new int[width * height][2];
595        }
596        int p = 0;
597        for (j = 0; j < three_state_map[0].length; j++) {
598            for (i = 0; i < three_state_map.length; i++) {
599                if (three_state_map[i][j] == GI_PARTIALLY_PRESENT) {
600                    //if it is partially present, do nothing
601                } else if ((j == 0 || three_state_map[i][j - 1] == GI_PARTIALLY_PRESENT)) {
602                    if (i > 0
603                            && (three_state_map[i - 1][j] == GI_FULLY_PRESENT
604                            || three_state_map[i - 1][j] == GI_ABSENCE)) {
605                        //use same as LHS
606                        three_state_map[i][j] = three_state_map[i - 1][j];
607                    } else {
608                        int count = 0;
609                        for(int k=startPolygon;k<=endPolygon;k++) {
610                            count += isWithin_EPSG900913(j * divx + divx / 2 + longitude1, i * divy + divy / 2 + latitude1)?1:0;
611                        }
612                        if(count % 2 == 1) {
613                            //if the previous was partially present, test
614                            three_state_map[i][j] = GI_FULLY_PRESENT;
615                        } //else absent
616                    } //else absent
617                } else {
618                    //if the previous was fully present, repeat
619                    //if the previous was absent, repeat
620                    three_state_map[i][j] = three_state_map[i][j - 1];
621                }
622
623                //apply to cells;
624                if (cellsReturned && three_state_map[i][j] != GI_UNDEFINED) {   //undefined == absence
625                    data[p][0] = j;
626                    data[p][1] = i;
627                    p++;
628                }
629            }
630        }
631        if (data != null) {
632            data = java.util.Arrays.copyOf(data, p);
633        }
634        return data;
635    }
636
637}