Revision 281

View differences:

org.gvsig.toolbox/tags/org.gvsig.toolbox-1.0.45/org.gvsig.toolbox.math/MANIFEST.MF
1
Manifest-Version: 1.0
2
Ant-Version: Apache Ant 1.7.0
3
Created-By: 16.3-b01 (Sun Microsystems Inc.)
4
Implementation-Version: 0.7
5
Built-Date: 2011-12-14 14:01:08
6

  
0 7

  
org.gvsig.toolbox/tags/org.gvsig.toolbox-1.0.45/org.gvsig.toolbox.math/src/main/java/com/infomatiq/jsi/Point.java
1
//   Point.java
2
//   Java Spatial Index Library
3
//   Copyright (C) 2002 Infomatiq Limited
4
//
5
//  This library is free software; you can redistribute it and/or
6
//  modify it under the terms of the GNU Lesser General Public
7
//  License as published by the Free Software Foundation; either
8
//  version 2.1 of the License, or (at your option) any later version.
9
//
10
//  This library is distributed in the hope that it will be useful,
11
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
//  Lesser General Public License for more details.
14
//
15
//  You should have received a copy of the GNU Lesser General Public
16
//  License along with this library; if not, write to the Free Software
17
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18

  
19
package com.infomatiq.jsi;
20

  
21
/**
22
 * Currently hardcoded to 2 dimensions, but could be extended.
23
 *
24
 * @author aled@sourceforge.net
25
 * @version 1.0b2p1
26
 */
27
public class Point {
28
   /**
29
    * Number of dimensions in a point. In theory this could be exended to three or more dimensions.
30
    */
31
   private final static int DIMENSIONS = 2;
32

  
33
   /**
34
    * The (x, y) coordinates of the point.
35
    */
36
   public float[]           coordinates;
37

  
38

  
39
   /**
40
    * Constructor.
41
    *
42
    * @param x
43
    *                The x coordinate of the point
44
    * @param y
45
    *                The y coordinate of the point
46
    */
47
   public Point(final float x,
48
                final float y) {
49
      coordinates = new float[DIMENSIONS];
50
      coordinates[0] = x;
51
      coordinates[1] = y;
52
   }
53
}
0 54

  
org.gvsig.toolbox/tags/org.gvsig.toolbox-1.0.45/org.gvsig.toolbox.math/src/main/java/com/infomatiq/jsi/rtree/Node.java
1
//   Node.java
2
//   Java Spatial Index Library
3
//   Copyright (C) 2002 Infomatiq Limited
4
//
5
//  This library is free software; you can redistribute it and/or
6
//  modify it under the terms of the GNU Lesser General Public
7
//  License as published by the Free Software Foundation; either
8
//  version 2.1 of the License, or (at your option) any later version.
9
//
10
//  This library is distributed in the hope that it will be useful,
11
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
//  Lesser General Public License for more details.
14
//
15
//  You should have received a copy of the GNU Lesser General Public
16
//  License along with this library; if not, write to the Free Software
17
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18

  
19
package com.infomatiq.jsi.rtree;
20

  
21
import com.infomatiq.jsi.Rectangle;
22

  
23
/**
24
 * <p>
25
 * Used by RTree. There are no public methods in this class.
26
 * </p>
27
 *
28
 * @author aled@sourceforge.net
29
 * @version 1.0b2p1
30
 */
31
public class Node {
32
   int         nodeId  = 0;
33
   Rectangle   mbr     = null;
34
   Rectangle[] entries = null;
35
   int[]       ids     = null;
36
   int         level;
37
   int         entryCount;
38

  
39

  
40
   Node(final int nodeId,
41
        final int level,
42
        final int maxNodeEntries) {
43
      this.nodeId = nodeId;
44
      this.level = level;
45
      entries = new Rectangle[maxNodeEntries];
46
      ids = new int[maxNodeEntries];
47
   }
48

  
49

  
50
   void addEntry(final Rectangle r,
51
                 final int id) {
52
      ids[entryCount] = id;
53
      entries[entryCount] = r.copy();
54
      entryCount++;
55
      if (mbr == null) {
56
         mbr = r.copy();
57
      }
58
      else {
59
         mbr.add(r);
60
      }
61
   }
62

  
63

  
64
   void addEntryNoCopy(final Rectangle r,
65
                       final int id) {
66
      ids[entryCount] = id;
67
      entries[entryCount] = r;
68
      entryCount++;
69
      if (mbr == null) {
70
         mbr = r.copy();
71
      }
72
      else {
73
         mbr.add(r);
74
      }
75
   }
76

  
77

  
78
   // Return the index of the found entry, or -1 if not found
79
   int findEntry(final Rectangle r,
80
                 final int id) {
81
      for (int i = 0; i < entryCount; i++) {
82
         if (id == ids[i] && r.equals(entries[i])) {
83
            return i;
84
         }
85
      }
86
      return -1;
87
   }
88

  
89

  
90
   // delete entry. This is done by setting it to null and copying the last entry into its space.
91
   void deleteEntry(final int i,
92
                    final int minNodeEntries) {
93
      final int lastIndex = entryCount - 1;
94
      final Rectangle deletedRectangle = entries[i];
95
      entries[i] = null;
96
      if (i != lastIndex) {
97
         entries[i] = entries[lastIndex];
98
         ids[i] = ids[lastIndex];
99
         entries[lastIndex] = null;
100
      }
101
      entryCount--;
102

  
103
      // if there are at least minNodeEntries, adjust the MBR.
104
      // otherwise, don't bother, as the node will be
105
      // eliminated anyway.
106
      if (entryCount >= minNodeEntries) {
107
         recalculateMBR(deletedRectangle);
108
      }
109
   }
110

  
111

  
112
   // oldRectangle is a rectangle that has just been deleted or made smaller.
113
   // Thus, the MBR is only recalculated if the OldRectangle influenced the old MBR
114
   void recalculateMBR(final Rectangle deletedRectangle) {
115
      if (mbr.edgeOverlaps(deletedRectangle)) {
116
         mbr.set(entries[0].min, entries[0].max);
117

  
118
         for (int i = 1; i < entryCount; i++) {
119
            mbr.add(entries[i]);
120
         }
121
      }
122
   }
123

  
124

  
125
   public int getEntryCount() {
126
      return entryCount;
127
   }
128

  
129

  
130
   public Rectangle getEntry(final int index) {
131
      if (index < entryCount) {
132
         return entries[index];
133
      }
134
      return null;
135
   }
136

  
137

  
138
   public int getId(final int index) {
139
      if (index < entryCount) {
140
         return ids[index];
141
      }
142
      return -1;
143
   }
144

  
145

  
146
   /**
147
    * eliminate null entries, move all entries to the start of the source node
148
    */
149
   void reorganize(final RTree rtree) {
150
      int countdownIndex = rtree.maxNodeEntries - 1;
151
      for (int index = 0; index < entryCount; index++) {
152
         if (entries[index] == null) {
153
            while (entries[countdownIndex] == null && countdownIndex > index) {
154
               countdownIndex--;
155
            }
156
            entries[index] = entries[countdownIndex];
157
            ids[index] = ids[countdownIndex];
158
            entries[countdownIndex] = null;
159
         }
160
      }
161
   }
162

  
163

  
164
   boolean isLeaf() {
165
      return (level == 1);
166
   }
167

  
168

  
169
   public int getLevel() {
170
      return level;
171
   }
172

  
173

  
174
   public Rectangle getMBR() {
175
      return mbr;
176
   }
177
}
0 178

  
org.gvsig.toolbox/tags/org.gvsig.toolbox-1.0.45/org.gvsig.toolbox.math/src/main/java/com/infomatiq/jsi/rtree/RTree.java
1
//   RTree.java
2
//   Java Spatial Index Library
3
//   Copyright (C) 2002 Infomatiq Limited
4
//   Copyright (C) 2008 Aled Morris <aled@sourceforge.net>
5
//
6
//  This library is free software; you can redistribute it and/or
7
//  modify it under the terms of the GNU Lesser General Public
8
//  License as published by the Free Software Foundation; either
9
//  version 2.1 of the License, or (at your option) any later version.
10
//
11
//  This library is distributed in the hope that it will be useful,
12
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
//  Lesser General Public License for more details.
15
//
16
//  You should have received a copy of the GNU Lesser General Public
17
//  License along with this library; if not, write to the Free Software
18
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19

  
20
package com.infomatiq.jsi.rtree;
21

  
22
import gnu.trove.TIntArrayList;
23
import gnu.trove.TIntObjectHashMap;
24
import gnu.trove.TIntProcedure;
25
import gnu.trove.TIntStack;
26

  
27
import java.util.Properties;
28

  
29
import com.infomatiq.jsi.IntProcedure;
30
import com.infomatiq.jsi.Point;
31
import com.infomatiq.jsi.Rectangle;
32
import com.infomatiq.jsi.SpatialIndex;
33

  
34
/**
35
 * <p>
36
 * This is a lightweight RTree implementation, specifically designed for the following features (in order of importance):
37
 * <ul>
38
 * <li>Fast intersection query performance. To achieve this, the RTree uses only main memory to store entries. Obviously this
39
 * will only improve performance if there is enough physical memory to avoid paging.</li>
40
 * <li>Low memory requirements.</li>
41
 * <li>Fast add performance.</li>
42
 * </ul>
43
 * </p>
44
 *
45
 * <p>
46
 * The main reason for the high speed of this RTree implementation is the avoidance of the creation of unnecessary objects, mainly
47
 * achieved by using primitive collections from the trove4j library.
48
 * </p>
49
 *
50
 * @author aled@sourceforge.net
51
 * @version 1.0b2p1
52
 */
53
public class RTree
54
         implements
55
            SpatialIndex {
56

  
57
   private static final String  version                       = "1.0b2p1";
58

  
59
   // parameters of the tree
60
   private final static int     DEFAULT_MAX_NODE_ENTRIES      = 10;
61
   int                          maxNodeEntries;
62
   int                          minNodeEntries;
63

  
64
   // map of nodeId -> node object
65
   // [x] TODO eliminate this map - it should not be needed. Nodes
66
   // can be found by traversing the tree.
67
   private final TIntObjectHashMap    nodeMap                       = new TIntObjectHashMap();
68

  
69
   // internal consistency checking - set to true if debugging tree corruption
70
   private final static boolean INTERNAL_CONSISTENCY_CHECKING = false;
71

  
72
   // used to mark the status of entries during a node split
73
   private final static int     ENTRY_STATUS_ASSIGNED         = 0;
74
   private final static int     ENTRY_STATUS_UNASSIGNED       = 1;
75
   private byte[]               entryStatus                   = null;
76
   private byte[]               initialEntryStatus            = null;
77

  
78
   // stacks used to store nodeId and entry index of each node
79
   // from the root down to the leaf. Enables fast lookup
80
   // of nodes when a split is propagated up the tree.
81
   private final TIntStack            parents                       = new TIntStack();
82
   private final TIntStack            parentsEntry                  = new TIntStack();
83

  
84
   // initialisation
85
   private int                  treeHeight                    = 1;                      // leaves are always level 1
86
   private int                  rootNodeId                    = 0;
87
   private int                  size                          = 0;
88

  
89
   // Enables creation of new nodes
90
   private int                  highestUsedNodeId             = rootNodeId;
91

  
92
   // Deleted node objects are retained in the nodeMap,
93
   // so that they can be reused. Store the IDs of nodes
94
   // which can be reused.
95
   private final TIntStack            deletedNodeIds                = new TIntStack();
96

  
97
   // List of nearest rectangles. Use a member variable to
98
   // avoid recreating the object each time nearest() is called.
99
   private final TIntArrayList        nearestIds                    = new TIntArrayList();
100

  
101
   // Inner class used as a bridge between the trove4j TIntProcedure
102
   // and the SpatialIndex IntProcedure. This is used because
103
   // the nearest rectangles must be stored as they are found, in
104
   // case a closer one is found later.
105
   //
106
   // A single instance of this class is used to avoid creating a new
107
   // one every time nearest() is called.
108
   private class TIntProcedureVisit
109
            implements
110
               TIntProcedure {
111
      public IntProcedure m_intProcedure = null;
112

  
113

  
114
      public void setProcedure(final IntProcedure ip) {
115
         m_intProcedure = ip;
116
      }
117

  
118

  
119
      public boolean execute(final int i) {
120
         m_intProcedure.execute(i);
121
         return true;
122
      }
123
   };
124

  
125
   private final TIntProcedureVisit visitProc = new TIntProcedureVisit();
126

  
127

  
128
   /**
129
    * Constructor. Use init() method to initialize parameters of the RTree.
130
    */
131
   public RTree() {
132
      return; // NOP
133
   }
134

  
135

  
136
   //-------------------------------------------------------------------------
137
   // public implementation of SpatialIndex interface:
138
   //  init(Properties)
139
   //  add(Rectangle, int)
140
   //  delete(Rectangle, int)
141
   //  nearest(Point, IntProcedure, float)
142
   //  intersects(Rectangle, IntProcedure)
143
   //  contains(Rectangle, IntProcedure)
144
   //  size()
145
   //-------------------------------------------------------------------------
146
   /**
147
    * <p>
148
    * Initialize implementation dependent properties of the RTree. Currently implemented properties are:
149
    * <ul>
150
    * <li>MaxNodeEntries</li>
151
    * This specifies the maximum number of entries in a node. The default value is 10, which is used if the property is not
152
    * specified, or is less than 2.
153
    * <li>MinNodeEntries</li>
154
    * This specifies the minimum number of entries in a node. The default value is half of the MaxNodeEntries value (rounded
155
    * down), which is used if the property is not specified or is less than 1.
156
    * </ul>
157
    * </p>
158
    *
159
    * @see com.infomatiq.jsi.SpatialIndex#init(Properties)
160
    */
161
   public void init(final Properties props) {
162
      maxNodeEntries = Integer.parseInt(props.getProperty("MaxNodeEntries", "0"));
163
      minNodeEntries = Integer.parseInt(props.getProperty("MinNodeEntries", "0"));
164

  
165
      // Obviously a node with less than 2 entries cannot be split.
166
      // The node splitting algorithm will work with only 2 entries
167
      // per node, but will be inefficient.
168
      if (maxNodeEntries < 2) {
169
         maxNodeEntries = DEFAULT_MAX_NODE_ENTRIES;
170
      }
171

  
172
      // The MinNodeEntries must be less than or equal to (int) (MaxNodeEntries / 2)
173
      if (minNodeEntries < 1 || minNodeEntries > maxNodeEntries / 2) {
174
         minNodeEntries = maxNodeEntries / 2;
175
      }
176

  
177
      entryStatus = new byte[maxNodeEntries];
178
      initialEntryStatus = new byte[maxNodeEntries];
179

  
180
      for (int i = 0; i < maxNodeEntries; i++) {
181
         initialEntryStatus[i] = ENTRY_STATUS_UNASSIGNED;
182
      }
183

  
184
      final Node root = new Node(rootNodeId, 1, maxNodeEntries);
185
      nodeMap.put(rootNodeId, root);
186

  
187
   }
188

  
189

  
190
   /**
191
    * @see com.infomatiq.jsi.SpatialIndex#add(Rectangle, int)
192
    */
193
   public void add(final Rectangle r,
194
                   final int id) {
195

  
196
      add(r.copy(), id, 1);
197

  
198
      size++;
199
   }
200

  
201

  
202
   /**
203
    * Adds a new entry at a specified level in the tree
204
    */
205
   private void add(final Rectangle r,
206
                    final int id,
207
                    final int level) {
208
      // I1 [Find position for new record] Invoke ChooseLeaf to select a
209
      // leaf node L in which to place r
210
      final Node n = chooseNode(r, level);
211
      Node newLeaf = null;
212

  
213
      // I2 [Add record to leaf node] If L has room for another entry,
214
      // install E. Otherwise invoke SplitNode to obtain L and LL containing
215
      // E and all the old entries of L
216
      if (n.entryCount < maxNodeEntries) {
217
         n.addEntryNoCopy(r, id);
218
      }
219
      else {
220
         newLeaf = splitNode(n, r, id);
221
      }
222

  
223
      // I3 [Propagate changes upwards] Invoke AdjustTree on L, also passing LL
224
      // if a split was performed
225
      final Node newNode = adjustTree(n, newLeaf);
226

  
227
      // I4 [Grow tree taller] If node split propagation caused the root to
228
      // split, create a new root whose children are the two resulting nodes.
229
      if (newNode != null) {
230
         final int oldRootNodeId = rootNodeId;
231
         final Node oldRoot = getNode(oldRootNodeId);
232

  
233
         rootNodeId = getNextNodeId();
234
         treeHeight++;
235
         final Node root = new Node(rootNodeId, treeHeight, maxNodeEntries);
236
         root.addEntry(newNode.mbr, newNode.nodeId);
237
         root.addEntry(oldRoot.mbr, oldRoot.nodeId);
238
         nodeMap.put(rootNodeId, root);
239
      }
240

  
241
      if (INTERNAL_CONSISTENCY_CHECKING) {
242
         checkConsistency(rootNodeId, treeHeight, null);
243
      }
244
   }
245

  
246

  
247
   /**
248
    * @see com.infomatiq.jsi.SpatialIndex#delete(Rectangle, int)
249
    */
250
   public boolean delete(final Rectangle r,
251
                         final int id) {
252
      // FindLeaf algorithm inlined here. Note the "official" algorithm
253
      // searches all overlapping entries. This seems inefficient to me,
254
      // as an entry is only worth searching if it contains (NOT overlaps)
255
      // the rectangle we are searching for.
256
      //
257
      // Also the algorithm has been changed so that it is not recursive.
258

  
259
      // FL1 [Search subtrees] If root is not a leaf, check each entry
260
      // to determine if it contains r. For each entry found, invoke
261
      // findLeaf on the node pointed to by the entry, until r is found or
262
      // all entries have been checked.
263
      parents.clear();
264
      parents.push(rootNodeId);
265

  
266
      parentsEntry.clear();
267
      parentsEntry.push(-1);
268
      Node n = null;
269
      int foundIndex = -1; // index of entry to be deleted in leaf
270

  
271
      while (foundIndex == -1 && parents.size() > 0) {
272
         n = getNode(parents.peek());
273
         final int startIndex = parentsEntry.peek() + 1;
274

  
275
         if (!n.isLeaf()) {
276

  
277
            boolean contains = false;
278
            for (int i = startIndex; i < n.entryCount; i++) {
279
               if (n.entries[i].contains(r)) {
280
                  parents.push(n.ids[i]);
281
                  parentsEntry.pop();
282
                  parentsEntry.push(i); // this becomes the start index when the child has been searched
283
                  parentsEntry.push(-1);
284
                  contains = true;
285
                  break; // ie go to next iteration of while()
286
               }
287
            }
288
            if (contains) {
289
               continue;
290
            }
291
         }
292
         else {
293
            foundIndex = n.findEntry(r, id);
294
         }
295

  
296
         parents.pop();
297
         parentsEntry.pop();
298
      } // while not found
299

  
300
      if (foundIndex != -1) {
301
         n.deleteEntry(foundIndex, minNodeEntries);
302
         condenseTree(n);
303
         size--;
304
      }
305

  
306
      // shrink the tree if possible (i.e. if root node has exactly one entry,and that
307
      // entry is not a leaf node, delete the root (it's entry becomes the new root)
308
      Node root = getNode(rootNodeId);
309
      while (root.entryCount == 1 && treeHeight > 1) {
310
         root.entryCount = 0;
311
         rootNodeId = root.ids[0];
312
         treeHeight--;
313
         root = getNode(rootNodeId);
314
      }
315

  
316
      return (foundIndex != -1);
317
   }
318

  
319

  
320
   public int nearest(final Point p) {
321

  
322
      final Node rootNode = getNode(rootNodeId);
323
      nearest(p, rootNode, Float.MAX_VALUE);
324
      final int iIndex = nearestIds.get(0);
325
      return iIndex;
326

  
327
   }
328

  
329

  
330
   /**
331
    * @see com.infomatiq.jsi.SpatialIndex#intersects(Rectangle, IntProcedure)
332
    */
333
   public void intersects(final Rectangle r,
334
                          final IntProcedure v) {
335
      final Node rootNode = getNode(rootNodeId);
336
      intersects(r, v, rootNode);
337
   }
338

  
339

  
340
   /**
341
    * @see com.infomatiq.jsi.SpatialIndex#contains(Rectangle, IntProcedure)
342
    */
343
   public void contains(final Rectangle r,
344
                        final IntProcedure v) {
345
      // find all rectangles in the tree that are contained by the passed rectangle
346
      // written to be non-recursive (should model other searches on this?)
347

  
348
      parents.clear();
349
      parents.push(rootNodeId);
350

  
351
      parentsEntry.clear();
352
      parentsEntry.push(-1);
353

  
354
      // TODO: possible shortcut here - could test for intersection with the
355
      // MBR of the root node. If no intersection, return immediately.
356

  
357
      while (parents.size() > 0) {
358
         final Node n = getNode(parents.peek());
359
         final int startIndex = parentsEntry.peek() + 1;
360

  
361
         if (!n.isLeaf()) {
362
            // go through every entry in the index node to check
363
            // if it intersects the passed rectangle. If so, it
364
            // could contain entries that are contained.
365
            boolean intersects = false;
366
            for (int i = startIndex; i < n.entryCount; i++) {
367
               if (r.intersects(n.entries[i])) {
368
                  parents.push(n.ids[i]);
369
                  parentsEntry.pop();
370
                  parentsEntry.push(i); // this becomes the start index when the child has been searched
371
                  parentsEntry.push(-1);
372
                  intersects = true;
373
                  break; // ie go to next iteration of while()
374
               }
375
            }
376
            if (intersects) {
377
               continue;
378
            }
379
         }
380
         else {
381
            // go through every entry in the leaf to check if
382
            // it is contained by the passed rectangle
383
            for (int i = 0; i < n.entryCount; i++) {
384
               if (r.contains(n.entries[i])) {
385
                  v.execute(n.ids[i]);
386
               }
387
            }
388
         }
389
         parents.pop();
390
         parentsEntry.pop();
391
      }
392
   }
393

  
394

  
395
   /**
396
    * @see com.infomatiq.jsi.SpatialIndex#size()
397
    */
398
   public int size() {
399
      return size;
400
   }
401

  
402

  
403
   /**
404
    * @see com.infomatiq.jsi.SpatialIndex#getBounds()
405
    */
406
   public Rectangle getBounds() {
407
      Rectangle bounds = null;
408

  
409
      final Node n = getNode(getRootNodeId());
410
      if (n != null && n.getMBR() != null) {
411
         bounds = n.getMBR().copy();
412
      }
413
      return bounds;
414
   }
415

  
416

  
417
   /**
418
    * @see com.infomatiq.jsi.SpatialIndex#getVersion()
419
    */
420
   public String getVersion() {
421
      return "RTree-" + version;
422
   }
423

  
424

  
425
   //-------------------------------------------------------------------------
426
   // end of SpatialIndex methods
427
   //-------------------------------------------------------------------------
428

  
429
   /**
430
    * Get the next available node ID. Reuse deleted node IDs if possible
431
    */
432
   private int getNextNodeId() {
433
      int nextNodeId = 0;
434
      if (deletedNodeIds.size() > 0) {
435
         nextNodeId = deletedNodeIds.pop();
436
      }
437
      else {
438
         nextNodeId = 1 + highestUsedNodeId++;
439
      }
440
      return nextNodeId;
441
   }
442

  
443

  
444
   /**
445
    * Get a node object, given the ID of the node.
446
    */
447
   public Node getNode(final int index) {
448
      return (Node) nodeMap.get(index);
449
   }
450

  
451

  
452
   /**
453
    * Get the highest used node ID
454
    */
455
   public int getHighestUsedNodeId() {
456
      return highestUsedNodeId;
457
   }
458

  
459

  
460
   /**
461
    * Get the root node ID
462
    */
463
   public int getRootNodeId() {
464
      return rootNodeId;
465
   }
466

  
467

  
468
   /**
469
    * Split a node. Algorithm is taken pretty much verbatim from Guttman's original paper.
470
    *
471
    * @return new node object.
472
    */
473
   private Node splitNode(final Node n,
474
                          final Rectangle newRect,
475
                          final int newId) {
476
      // [Pick first entry for each group] Apply algorithm pickSeeds to
477
      // choose two entries to be the first elements of the groups. Assign
478
      // each to a group.
479

  
480
      // debug code
481
      float initialArea = 0;
482
      //if (log.isDebugEnabled()) {
483
      final Rectangle union = n.mbr.union(newRect);
484
      initialArea = union.area();
485
      //}
486

  
487
      System.arraycopy(initialEntryStatus, 0, entryStatus, 0, maxNodeEntries);
488

  
489
      Node newNode = null;
490
      newNode = new Node(getNextNodeId(), n.level, maxNodeEntries);
491
      nodeMap.put(newNode.nodeId, newNode);
492

  
493
      pickSeeds(n, newRect, newId, newNode); // this also sets the entryCount to 1
494

  
495
      // [Check if done] If all entries have been assigned, stop. If one
496
      // group has so few entries that all the rest must be assigned to it in
497
      // order for it to have the minimum number m, assign them and stop.
498
      while (n.entryCount + newNode.entryCount < maxNodeEntries + 1) {
499
         if (maxNodeEntries + 1 - newNode.entryCount == minNodeEntries) {
500
            // assign all remaining entries to original node
501
            for (int i = 0; i < maxNodeEntries; i++) {
502
               if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) {
503
                  entryStatus[i] = ENTRY_STATUS_ASSIGNED;
504
                  n.mbr.add(n.entries[i]);
505
                  n.entryCount++;
506
               }
507
            }
508
            break;
509
         }
510
         if (maxNodeEntries + 1 - n.entryCount == minNodeEntries) {
511
            // assign all remaining entries to new node
512
            for (int i = 0; i < maxNodeEntries; i++) {
513
               if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) {
514
                  entryStatus[i] = ENTRY_STATUS_ASSIGNED;
515
                  newNode.addEntryNoCopy(n.entries[i], n.ids[i]);
516
                  n.entries[i] = null;
517
               }
518
            }
519
            break;
520
         }
521

  
522
         // [Select entry to assign] Invoke algorithm pickNext to choose the
523
         // next entry to assign. Add it to the group whose covering rectangle
524
         // will have to be enlarged least to accommodate it. Resolve ties
525
         // by adding the entry to the group with smaller area, then to the
526
         // the one with fewer entries, then to either. Repeat from S2
527
         pickNext(n, newNode);
528
      }
529

  
530
      n.reorganize(this);
531

  
532
      // check that the MBR stored for each node is correct.
533
      if (INTERNAL_CONSISTENCY_CHECKING) {
534
         if (!n.mbr.equals(calculateMBR(n))) {
535
            //log.error("Error: splitNode old node MBR wrong");
536
         }
537

  
538
         if (!newNode.mbr.equals(calculateMBR(newNode))) {
539
            //log.error("Error: splitNode new node MBR wrong");
540
         }
541
      }
542

  
543
      // debug code
544
      //    if (log.isDebugEnabled()) {
545
      //      float newArea = n.mbr.area() + newNode.mbr.area();
546
      //      float percentageIncrease = (100 * (newArea - initialArea)) / initialArea;
547
      //      log.debug("Node " + n.nodeId + " split. New area increased by " + percentageIncrease + "%");
548
      //    }
549

  
550
      return newNode;
551
   }
552

  
553

  
554
   /**
555
    * Pick the seeds used to split a node. Select two entries to be the first elements of the groups
556
    */
557
   private void pickSeeds(final Node n,
558
                          final Rectangle newRect,
559
                          final int newId,
560
                          final Node newNode) {
561
      // Find extreme rectangles along all dimension. Along each dimension,
562
      // find the entry whose rectangle has the highest low side, and the one
563
      // with the lowest high side. Record the separation.
564
      float maxNormalizedSeparation = 0;
565
      int highestLowIndex = 0;
566
      int lowestHighIndex = 0;
567

  
568
      // for the purposes of picking seeds, take the MBR of the node to include
569
      // the new rectangle aswell.
570
      n.mbr.add(newRect);
571

  
572
      //    if (log.isDebugEnabled()) {
573
      //      log.debug("pickSeeds(): NodeId = " + n.nodeId + ", newRect = " + newRect);
574
      //    }
575

  
576
      for (int d = 0; d < Rectangle.DIMENSIONS; d++) {
577
         float tempHighestLow = newRect.min[d];
578
         int tempHighestLowIndex = -1; // -1 indicates the new rectangle is the seed
579

  
580
         float tempLowestHigh = newRect.max[d];
581
         int tempLowestHighIndex = -1;
582

  
583
         for (int i = 0; i < n.entryCount; i++) {
584
            final float tempLow = n.entries[i].min[d];
585
            if (tempLow >= tempHighestLow) {
586
               tempHighestLow = tempLow;
587
               tempHighestLowIndex = i;
588
            }
589
            else { // ensure that the same index cannot be both lowestHigh and highestLow
590
               final float tempHigh = n.entries[i].max[d];
591
               if (tempHigh <= tempLowestHigh) {
592
                  tempLowestHigh = tempHigh;
593
                  tempLowestHighIndex = i;
594
               }
595
            }
596

  
597
            // PS2 [Adjust for shape of the rectangle cluster] Normalize the separations
598
            // by dividing by the widths of the entire set along the corresponding
599
            // dimension
600
            final float normalizedSeparation = (tempHighestLow - tempLowestHigh) / (n.mbr.max[d] - n.mbr.min[d]);
601

  
602
            if (normalizedSeparation > 1 || normalizedSeparation < -1) {
603
               //log.error("Invalid normalized separation");
604
            }
605

  
606
            //        if (log.isDebugEnabled()) {
607
            //          log.debug("Entry " + i + ", dimension " + d + ": HighestLow = " + tempHighestLow +
608
            //                    " (index " + tempHighestLowIndex + ")" + ", LowestHigh = " +
609
            //                    tempLowestHigh + " (index " + tempLowestHighIndex + ", NormalizedSeparation = " + normalizedSeparation);
610
            //        }
611

  
612
            // PS3 [Select the most extreme pair] Choose the pair with the greatest
613
            // normalized separation along any dimension.
614
            if (normalizedSeparation > maxNormalizedSeparation) {
615
               maxNormalizedSeparation = normalizedSeparation;
616
               highestLowIndex = tempHighestLowIndex;
617
               lowestHighIndex = tempLowestHighIndex;
618
            }
619
         }
620
      }
621

  
622
      // highestLowIndex is the seed for the new node.
623
      if (highestLowIndex == -1) {
624
         newNode.addEntry(newRect, newId);
625
      }
626
      else {
627
         newNode.addEntryNoCopy(n.entries[highestLowIndex], n.ids[highestLowIndex]);
628
         n.entries[highestLowIndex] = null;
629

  
630
         // move the new rectangle into the space vacated by the seed for the new node
631
         n.entries[highestLowIndex] = newRect;
632
         n.ids[highestLowIndex] = newId;
633
      }
634

  
635
      // lowestHighIndex is the seed for the original node.
636
      if (lowestHighIndex == -1) {
637
         lowestHighIndex = highestLowIndex;
638
      }
639

  
640
      entryStatus[lowestHighIndex] = ENTRY_STATUS_ASSIGNED;
641
      n.entryCount = 1;
642
      n.mbr.set(n.entries[lowestHighIndex].min, n.entries[lowestHighIndex].max);
643
   }
644

  
645

  
646
   /**
647
    * Pick the next entry to be assigned to a group during a node split.
648
    *
649
    * [Determine cost of putting each entry in each group] For each entry not yet in a group, calculate the area increase required
650
    * in the covering rectangles of each group
651
    */
652
   private int pickNext(final Node n,
653
                        final Node newNode) {
654
      float maxDifference = Float.NEGATIVE_INFINITY;
655
      int next = 0;
656
      int nextGroup = 0;
657

  
658
      maxDifference = Float.NEGATIVE_INFINITY;
659

  
660
      //    if (log.isDebugEnabled()) {
661
      //      log.debug("pickNext()");
662
      //    }
663

  
664
      for (int i = 0; i < maxNodeEntries; i++) {
665
         if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) {
666

  
667
            //        if (n.entries[i] == null) {
668
            //          log.error("Error: Node " + n.nodeId + ", entry " + i + " is null");
669
            //        }
670

  
671
            final float nIncrease = n.mbr.enlargement(n.entries[i]);
672
            final float newNodeIncrease = newNode.mbr.enlargement(n.entries[i]);
673
            final float difference = Math.abs(nIncrease - newNodeIncrease);
674

  
675
            if (difference > maxDifference) {
676
               next = i;
677

  
678
               if (nIncrease < newNodeIncrease) {
679
                  nextGroup = 0;
680
               }
681
               else if (newNodeIncrease < nIncrease) {
682
                  nextGroup = 1;
683
               }
684
               else if (n.mbr.area() < newNode.mbr.area()) {
685
                  nextGroup = 0;
686
               }
687
               else if (newNode.mbr.area() < n.mbr.area()) {
688
                  nextGroup = 1;
689
               }
690
               else if (newNode.entryCount < maxNodeEntries / 2) {
691
                  nextGroup = 0;
692
               }
693
               else {
694
                  nextGroup = 1;
695
               }
696
               maxDifference = difference;
697
            }
698
            //        if (log.isDebugEnabled()) {
699
            //          log.debug("Entry " + i + " group0 increase = " + nIncrease + ", group1 increase = " + newNodeIncrease +
700
            //                    ", diff = " + difference + ", MaxDiff = " + maxDifference + " (entry " + next + ")");
701
            //        }
702
         }
703
      }
704

  
705
      entryStatus[next] = ENTRY_STATUS_ASSIGNED;
706

  
707
      if (nextGroup == 0) {
708
         n.mbr.add(n.entries[next]);
709
         n.entryCount++;
710
      }
711
      else {
712
         // move to new node.
713
         newNode.addEntryNoCopy(n.entries[next], n.ids[next]);
714
         n.entries[next] = null;
715
      }
716

  
717
      return next;
718
   }
719

  
720

  
721
   /**
722
    * Recursively searches the tree for the nearest entry. Other queries call execute() on an IntProcedure when a matching entry
723
    * is found; however nearest() must store the entry Ids as it searches the tree, in case a nearer entry is found. Uses the
724
    * member variable nearestIds to store the nearest entry IDs.
725
    *
726
    * [x] TODO rewrite this to be non-recursive?
727
    */
728
   private float nearest(final Point p,
729
                         final Node n,
730
                         float nearestDistance) {
731
      for (int i = 0; i < n.entryCount; i++) {
732
         final float tempDistance = n.entries[i].distance(p);
733
         if (n.isLeaf()) { // for leaves, the distance is an actual nearest distance
734
            if (tempDistance < nearestDistance) {
735
               nearestDistance = tempDistance;
736
               nearestIds.clear();
737
            }
738
            if (tempDistance <= nearestDistance) {
739
               nearestIds.add(n.ids[i]);
740
            }
741
         }
742
         else { // for index nodes, only go into them if they potentially could have
743
            // a rectangle nearer than actualNearest
744
            if (tempDistance <= nearestDistance) {
745
               // search the child node
746
               nearestDistance = nearest(p, getNode(n.ids[i]), nearestDistance);
747
            }
748
         }
749
      }
750
      return nearestDistance;
751
   }
752

  
753

  
754
   /**
755
    * Recursively searches the tree for all intersecting entries. Immediately calls execute() on the passed IntProcedure when a
756
    * matching entry is found.
757
    *
758
    * [x] TODO rewrite this to be non-recursive? Make sure it doesn't slow it down.
759
    */
760
   private void intersects(final Rectangle r,
761
                           final IntProcedure v,
762
                           final Node n) {
763
      for (int i = 0; i < n.entryCount; i++) {
764
         if (r.intersects(n.entries[i])) {
765
            if (n.isLeaf()) {
766
               v.execute(n.ids[i]);
767
            }
768
            else {
769
               final Node childNode = getNode(n.ids[i]);
770
               intersects(r, v, childNode);
771
            }
772
         }
773
      }
774
   }
775

  
776
   /**
777
    * Used by delete(). Ensures that all nodes from the passed node up to the root have the minimum number of entries.
778
    *
779
    * Note that the parent and parentEntry stacks are expected to contain the nodeIds of all parents up to the root.
780
    */
781
   private final Rectangle oldRectangle = new Rectangle(0, 0, 0, 0);
782

  
783

  
784
   private void condenseTree(final Node l) {
785
      // CT1 [Initialize] Set n=l. Set the list of eliminated
786
      // nodes to be empty.
787
      Node n = l;
788
      Node parent = null;
789
      int parentEntry = 0;
790

  
791
      final TIntStack eliminatedNodeIds = new TIntStack();
792

  
793
      // CT2 [Find parent entry] If N is the root, go to CT6. Otherwise
794
      // let P be the parent of N, and let En be N's entry in P
795
      while (n.level != treeHeight) {
796
         parent = getNode(parents.pop());
797
         parentEntry = parentsEntry.pop();
798

  
799
         // CT3 [Eliminiate under-full node] If N has too few entries,
800
         // delete En from P and add N to the list of eliminated nodes
801
         if (n.entryCount < minNodeEntries) {
802
            parent.deleteEntry(parentEntry, minNodeEntries);
803
            eliminatedNodeIds.push(n.nodeId);
804
         }
805
         else {
806
            // CT4 [Adjust covering rectangle] If N has not been eliminated,
807
            // adjust EnI to tightly contain all entries in N
808
            if (!n.mbr.equals(parent.entries[parentEntry])) {
809
               oldRectangle.set(parent.entries[parentEntry].min, parent.entries[parentEntry].max);
810
               parent.entries[parentEntry].set(n.mbr.min, n.mbr.max);
811
               parent.recalculateMBR(oldRectangle);
812
            }
813
         }
814
         // CT5 [Move up one level in tree] Set N=P and repeat from CT2
815
         n = parent;
816
      }
817

  
818
      // CT6 [Reinsert orphaned entries] Reinsert all entries of nodes in set Q.
819
      // Entries from eliminated leaf nodes are reinserted in tree leaves as in
820
      // Insert(), but entries from higher level nodes must be placed higher in
821
      // the tree, so that leaves of their dependent subtrees will be on the same
822
      // level as leaves of the main tree
823
      while (eliminatedNodeIds.size() > 0) {
824
         final Node e = getNode(eliminatedNodeIds.pop());
825
         for (int j = 0; j < e.entryCount; j++) {
826
            add(e.entries[j], e.ids[j], e.level);
827
            e.entries[j] = null;
828
         }
829
         e.entryCount = 0;
830
         deletedNodeIds.push(e.nodeId);
831
      }
832
   }
833

  
834

  
835
   /**
836
    * Used by add(). Chooses a leaf to add the rectangle to.
837
    */
838
   private Node chooseNode(final Rectangle r,
839
                           final int level) {
840
      // CL1 [Initialize] Set N to be the root node
841
      Node n = getNode(rootNodeId);
842
      parents.clear();
843
      parentsEntry.clear();
844

  
845
      // CL2 [Leaf check] If N is a leaf, return N
846
      while (true) {
847
         //      if (n == null) {
848
         //        log.error("Could not get root node (" + rootNodeId + ")");
849
         //      }
850

  
851
         if (n.level == level) {
852
            return n;
853
         }
854

  
855
         // CL3 [Choose subtree] If N is not at the desired level, let F be the entry in N
856
         // whose rectangle FI needs least enlargement to include EI. Resolve
857
         // ties by choosing the entry with the rectangle of smaller area.
858
         float leastEnlargement = n.getEntry(0).enlargement(r);
859
         int index = 0; // index of rectangle in subtree
860
         for (int i = 1; i < n.entryCount; i++) {
861
            final Rectangle tempRectangle = n.getEntry(i);
862
            final float tempEnlargement = tempRectangle.enlargement(r);
863
            if ((tempEnlargement < leastEnlargement)
864
                || ((tempEnlargement == leastEnlargement) && (tempRectangle.area() < n.getEntry(index).area()))) {
865
               index = i;
866
               leastEnlargement = tempEnlargement;
867
            }
868
         }
869

  
870
         parents.push(n.nodeId);
871
         parentsEntry.push(index);
872

  
873
         // CL4 [Descend until a leaf is reached] Set N to be the child node
874
         // pointed to by Fp and repeat from CL2
875
         n = getNode(n.ids[index]);
876
      }
877
   }
878

  
879

  
880
   /**
881
    * Ascend from a leaf node L to the root, adjusting covering rectangles and propagating node splits as necessary.
882
    */
883
   private Node adjustTree(Node n,
884
                           Node nn) {
885
      // AT1 [Initialize] Set N=L. If L was split previously, set NN to be
886
      // the resulting second node.
887

  
888
      // AT2 [Check if done] If N is the root, stop
889
      while (n.level != treeHeight) {
890

  
891
         // AT3 [Adjust covering rectangle in parent entry] Let P be the parent
892
         // node of N, and let En be N's entry in P. Adjust EnI so that it tightly
893
         // encloses all entry rectangles in N.
894
         Node parent = getNode(parents.pop());
895
         final int entry = parentsEntry.pop();
896

  
897
         //      if (parent.ids[entry] != n.nodeId) {
898
         //        log.error("Error: entry " + entry + " in node " +
899
         //             parent.nodeId + " should point to node " +
900
         //             n.nodeId + "; actually points to node " + parent.ids[entry]);
901
         //      }
902

  
903
         if (!parent.entries[entry].equals(n.mbr)) {
904
            parent.entries[entry].set(n.mbr.min, n.mbr.max);
905
            parent.mbr.set(parent.entries[0].min, parent.entries[0].max);
906
            for (int i = 1; i < parent.entryCount; i++) {
907
               parent.mbr.add(parent.entries[i]);
908
            }
909
         }
910

  
911
         // AT4 [Propagate node split upward] If N has a partner NN resulting from
912
         // an earlier split, create a new entry Enn with Ennp pointing to NN and
913
         // Enni enclosing all rectangles in NN. Add Enn to P if there is room.
914
         // Otherwise, invoke splitNode to produce P and PP containing Enn and
915
         // all P's old entries.
916
         Node newNode = null;
917
         if (nn != null) {
918
            if (parent.entryCount < maxNodeEntries) {
919
               parent.addEntry(nn.mbr, nn.nodeId);
920
            }
921
            else {
922
               newNode = splitNode(parent, nn.mbr.copy(), nn.nodeId);
923
            }
924
         }
925

  
926
         // AT5 [Move up to next level] Set N = P and set NN = PP if a split
927
         // occurred. Repeat from AT2
928
         n = parent;
929
         nn = newNode;
930

  
931
         parent = null;
932
         newNode = null;
933
      }
934

  
935
      return nn;
936
   }
937

  
938

  
939
   /**
940
    * Check the consistency of the tree.
941
    */
942
   private void checkConsistency(final int nodeId,
943
                                 final int expectedLevel,
944
                                 final Rectangle expectedMBR) {
945
      // go through the tree, and check that the internal data structures of
946
      // the tree are not corrupted.
947
      final Node n = getNode(nodeId);
948

  
949
      if (n == null) {
950
         //log.error("Error: Could not read node " + nodeId);
951
      }
952

  
953
      if (n.level != expectedLevel) {
954
         //log.error("Error: Node " + nodeId + ", expected level " + expectedLevel + ", actual level " + n.level);
955
      }
956

  
957
      final Rectangle calculatedMBR = calculateMBR(n);
958

  
959
      if (!n.mbr.equals(calculatedMBR)) {
960
         //log.error("Error: Node " + nodeId + ", calculated MBR does not equal stored MBR");
961
      }
962

  
963
      if (expectedMBR != null && !n.mbr.equals(expectedMBR)) {
964
         // log.error("Error: Node " + nodeId + ", expected MBR (from parent) does not equal stored MBR");
965
      }
966

  
967
      // Check for corruption where a parent entry is the same object as the child MBR
968
      if (expectedMBR != null && n.mbr.sameObject(expectedMBR)) {
969
         //log.error("Error: Node " + nodeId + " MBR using same rectangle object as parent's entry");
970
      }
971

  
972
      for (int i = 0; i < n.entryCount; i++) {
973
         if (n.entries[i] == null) {
974
            //log.error("Error: Node " + nodeId + ", Entry " + i + " is null");
975
         }
976

  
977
         if (n.level > 1) { // if not a leaf
978
            checkConsistency(n.ids[i], n.level - 1, n.entries[i]);
979
         }
980
      }
981
   }
982

  
983

  
984
   /**
985
    * Given a node object, calculate the node MBR from it's entries. Used in consistency checking
986
    */
987
   private Rectangle calculateMBR(final Node n) {
988
      final Rectangle mbr = new Rectangle(n.entries[0].min, n.entries[0].max);
989

  
990
      for (int i = 1; i < n.entryCount; i++) {
991
         mbr.add(n.entries[i]);
992
      }
993
      return mbr;
994
   }
995
}
0 996

  
org.gvsig.toolbox/tags/org.gvsig.toolbox-1.0.45/org.gvsig.toolbox.math/src/main/java/com/infomatiq/jsi/IntProcedure.java
1
//   IntProcedure.java
2
//   Java Spatial Index Library
3
//   Copyright (C) 2002 Infomatiq Limited
4
//
5
//  This library is free software; you can redistribute it and/or
6
//  modify it under the terms of the GNU Lesser General Public
7
//  License as published by the Free Software Foundation; either
8
//  version 2.1 of the License, or (at your option) any later version.
9
//
10
//  This library is distributed in the hope that it will be useful,
11
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
//  Lesser General Public License for more details.
14
//
15
//  You should have received a copy of the GNU Lesser General Public
16
//  License along with this library; if not, write to the Free Software
17
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18

  
19
package com.infomatiq.jsi;
20

  
21
/**
22
 * Interface that defines a procedure to be executed, that takes an int parameter
23
 *
24
 * @author aled@sourceforge.net
25
 * @version 1.0b2p1
26
 */
27
public interface IntProcedure {
28
   /**
29
    * @param id
30
    *                integer value
31
    *
32
    * @return flag to indicate whether to continue executing the procedure. Return true to continue executing, or false to prevent
33
    *         any more calls to this method.
34
    */
35
   public boolean execute(int id);
36
}
0 37

  
org.gvsig.toolbox/tags/org.gvsig.toolbox-1.0.45/org.gvsig.toolbox.math/src/main/java/com/infomatiq/jsi/Rectangle.java
1
//   Rectangle.java
2
//   Java Spatial Index Library
3
//   Copyright (C) 2002 Infomatiq Limited
4
//
5
//  This library is free software; you can redistribute it and/or
6
//  modify it under the terms of the GNU Lesser General Public
7
//  License as published by the Free Software Foundation; either
8
//  version 2.1 of the License, or (at your option) any later version.
9
//
10
//  This library is distributed in the hope that it will be useful,
11
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
//  Lesser General Public License for more details.
14
//
15
//  You should have received a copy of the GNU Lesser General Public
16
//  License along with this library; if not, write to the Free Software
17
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18

  
19
package com.infomatiq.jsi;
20

  
21
import java.util.Arrays;
22

  
23
/**
24
 * Currently hardcoded to 2 dimensions, but could be extended.
25
 *
26
 * @author aled@sourceforge.net
27
 * @version 1.0b2p1
28
 */
29
public class Rectangle {
30
   /**
31
    * Number of dimensions in a rectangle. In theory this could be exended to three or more dimensions.
32
    */
33
   public final static int DIMENSIONS = 2;
34

  
35
   /**
36
    * array containing the minimum value for each dimension; ie { min(x), min(y) }
37
    */
38
   public float[]          max;
39

  
40
   /**
41
    * array containing the maximum value for each dimension; ie { max(x), max(y) }
42
    */
43
   public float[]          min;
44

  
45

  
46
   /**
47
    * Constructor.
48
    *
49
    * @param x1
50
    *                coordinate of any corner of the rectangle
51
    * @param y1
52
    *                (see x1)
53
    * @param x2
54
    *                coordinate of the opposite corner
55
    * @param y2
56
    *                (see x2)
57
    */
58
   public Rectangle(final float x1,
59
                    final float y1,
60
                    final float x2,
61
                    final float y2) {
62
      min = new float[DIMENSIONS];
63
      max = new float[DIMENSIONS];
64
      set(x1, y1, x2, y2);
65
   }
66

  
67

  
68
   /**
69
    * Constructor.
70
    *
71
    * @param min
72
    *                array containing the minimum value for each dimension; ie { min(x), min(y) }
73
    * @param max
74
    *                array containing the maximum value for each dimension; ie { max(x), max(y) }
75
    */
76
   public Rectangle(final float[] min,
77
                    final float[] max) {
78
      if (min.length != DIMENSIONS || max.length != DIMENSIONS) {
79
         throw new RuntimeException("Error in Rectangle constructor: " + "min and max arrays must be of length " + DIMENSIONS);
80
      }
81

  
82
      this.min = new float[DIMENSIONS];
83
      this.max = new float[DIMENSIONS];
84

  
85
      set(min, max);
86
   }
87

  
88

  
89
   /**
90
    * Sets the size of the rectangle.
91
    *
92
    * @param x1
93
    *                coordinate of any corner of the rectangle
94
    * @param y1
95
    *                (see x1)
96
    * @param x2
97
    *                coordinate of the opposite corner
98
    * @param y2
99
    *                (see x2)
100
    */
101
   public void set(final float x1,
102
                   final float y1,
103
                   final float x2,
104
                   final float y2) {
105
      min[0] = Math.min(x1, x2);
106
      min[1] = Math.min(y1, y2);
107
      max[0] = Math.max(x1, x2);
108
      max[1] = Math.max(y1, y2);
109
   }
110

  
111

  
112
   /**
113
    * Sets the size of the rectangle.
114
    *
115
    * @param min
116
    *                array containing the minimum value for each dimension; ie { min(x), min(y) }
117
    * @param max
118
    *                array containing the maximum value for each dimension; ie { max(x), max(y) }
119
    */
120
   public void set(final float[] min,
121
                   final float[] max) {
122
      System.arraycopy(min, 0, this.min, 0, DIMENSIONS);
123
      System.arraycopy(max, 0, this.max, 0, DIMENSIONS);
124
   }
125

  
126

  
127
   /**
128
    * Make a copy of this rectangle
129
    *
130
    * @return copy of this rectangle
131
    */
132
   public Rectangle copy() {
133
      return new Rectangle(min, max);
134
   }
135

  
136

  
137
   /**
138
    * Determine whether an edge of this rectangle overlies the equivalent edge of the passed rectangle
139
    */
140
   public boolean edgeOverlaps(final Rectangle r) {
141
      for (int i = 0; i < DIMENSIONS; i++) {
142
         if (min[i] == r.min[i] || max[i] == r.max[i]) {
143
            return true;
144
         }
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff