001: /**
002: *
003: */package net.refractions.udig.project.render;
004:
005: import java.awt.Dimension;
006: import java.awt.Point;
007: import java.awt.Rectangle;
008: import java.awt.geom.AffineTransform;
009: import java.awt.geom.Dimension2D;
010: import java.util.HashSet;
011: import java.util.Random;
012: import java.util.Set;
013:
014: import com.vividsolutions.jts.geom.Coordinate;
015: import com.vividsolutions.jts.geom.Envelope;
016:
017: /**
018: * A helper class which divides an area into tiles or a minimum size. Provides a
019: * number of convenience methods.
020: *
021: * @author jones
022: */
023: public class TileCalculator {
024: private AffineTransform worldToScreen;
025:
026: private Dimension tileSize;
027:
028: private Envelope bounds;
029:
030: private int numXTiles;
031:
032: private int numYTiles;
033:
034: private Coordinate worldTileSize;
035:
036: private Rectangle rect;
037:
038: /**
039: * Creates a new instance, the bounds must be set before it is ready to be used.
040: *
041: * @param worldToScreen the transform from world coordinates to screen coordinates
042: * @param tileSize the size of the tiles to create.
043: */
044: public TileCalculator(AffineTransform worldToScreen,
045: Dimension tileSize) {
046: this .worldToScreen = worldToScreen;
047: this .tileSize = tileSize;
048: }
049:
050: /**
051: * Creates a new instance. It is ready to be queried immediately.
052: *
053: * @param worldToScreen the transform from world coordinates to screen coordinates
054: * @param tileSize the size of the tiles to create.
055: */
056: public TileCalculator(AffineTransform worldToScreen,
057: Dimension tileSize, Envelope bounds) {
058: this .worldToScreen = worldToScreen;
059: this .tileSize = tileSize;
060: this .bounds = bounds;
061: reset();
062: }
063:
064: /**
065: * Returns the number of tiles in the X-direction
066: * @return
067: */
068: public int numXTiles() {
069: validateState();
070: return numXTiles;
071: }
072:
073: private void validateState() {
074: if (bounds == null)
075: throw new IllegalStateException(
076: "Bounds must be set before TileCalculator may be used"); //$NON-NLS-1$
077: if (tileSize == null)
078: throw new IllegalStateException(
079: "Tile Size must be set before TileCalculator may be used"); //$NON-NLS-1$
080: if (worldToScreen == null)
081: throw new IllegalStateException(
082: "World to Screen transform must be set before TileCalculator may be used"); //$NON-NLS-1$
083: }
084:
085: /**
086: * Returns the number of tiles in the Y-direction
087: * @return
088: */
089: public int numYTiles() {
090: validateState();
091: return numYTiles;
092: }
093:
094: /**
095: * Return the Envelope (world coordinates) of the tile indexed by x and y.
096: * @return the Envelope (world coordinates) of the tile indexed by x and y.
097: */
098: public Envelope getWorldTile(int x, int y) {
099: validateState();
100: double xmin = bounds.getMinX() + worldTileSize.x * x;
101: double ymin = bounds.getMinY() + worldTileSize.y * y;
102:
103: return new Envelope(xmin, Math.min(xmin + worldTileSize.x,
104: bounds.getMaxX()), ymin, Math.min(ymin
105: + worldTileSize.y, bounds.getMaxY()));
106: }
107:
108: /**
109: * Return the Rectangle (screen coordinates) of the tile indexed by x and y.
110: * @return the Rectangle (screen coordinates) of the tile indexed by x and y.
111: */
112: public Rectangle getScreenTile(int x, int y) {
113: validateState();
114: int xmin = rect.x + tileSize.width * x;
115: int ymin = rect.y + tileSize.height * y;
116: int width = Math.min(tileSize.width, (int) rect.getMaxX()
117: - xmin);
118: int height = Math.min(tileSize.height, (int) rect.getMaxY()
119: - ymin);
120: return new Rectangle(xmin, ymin, width, height);
121: }
122:
123: /**
124: * Calculates where and how many tiles there are.
125: */
126: private void reset() {
127: double[] points = new double[] { bounds.getMinX(),
128: bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY() };
129: worldToScreen.transform(points, 0, points, 0, 2);
130: rect = new Rectangle((int) points[0], (int) points[1], Math
131: .abs((int) (points[2] - points[0])), Math
132: .abs((int) (points[3] - points[1])));
133:
134: numXTiles = (int) Math.ceil(rect.getWidth()
135: / tileSize.getWidth());
136: numYTiles = (int) Math.ceil(rect.getHeight()
137: / tileSize.getHeight());
138:
139: worldTileSize = new Coordinate(bounds.getWidth() / numXTiles,
140: bounds.getHeight() / numYTiles);
141: }
142:
143: Set<Point> randomMap;
144: Random random = new Random();
145:
146: /**
147: * Resets so that the getRandom() will clear its cache of visited tiles.
148: */
149: public void resetRandomizer() {
150: validateState();
151: randomMap = new HashSet<Point>();
152: for (int x = 0; x < numXTiles; x++) {
153: for (int y = 0; y < numYTiles; y++) {
154: randomMap.add(new Point(x, y));
155: }
156:
157: }
158: }
159:
160: /**
161: * Gets a random tile in world coordinates. Each tile is visited only once and after all the tiles
162: * have been visited null will be returned.
163: * <p><b>WARNING:</b>
164: * If each tile is visited one and only once regardless of whether {@link #getWorldRandom()} or
165: * {@link #getScreenRandom()}
166: * is used
167: * </p>
168: *
169: * @return a random tile in world coordinates.
170: */
171: public Envelope getWorldRandom() {
172: if (randomMap == null) {
173: validateState();
174: resetRandomizer();
175: }
176: if (randomMap.isEmpty()) {
177: return null;
178: }
179: int x = random.nextInt(numXTiles);
180: int y = random.nextInt(numXTiles);
181: Point point = new Point(x, y);
182: while (!randomMap.contains(point)) {
183: x = random.nextInt(numXTiles);
184: y = random.nextInt(numXTiles);
185: point = new Point(x, y);
186: }
187: randomMap.remove(point);
188: return getWorldTile(x, y);
189: }
190:
191: /**
192: * Gets a random tile in screen coordinates. Each tile is visited only once and after all the tiles
193: * have been visited null will be returned.
194: * <p><b>WARNING:</b>
195: * If each tile is visited one and only once regardless of whether {@link #getWorldRandom()} or
196: * {@link #getScreenRandom()}
197: * is used
198: * </p>
199: *
200: * @return a random tile in screen coordinates
201: */
202: public Rectangle getScreenRandom() {
203: if (randomMap == null) {
204: validateState();
205: resetRandomizer();
206: }
207: if (randomMap.isEmpty()) {
208: return null;
209: }
210: int x = random.nextInt(numXTiles);
211: int y = random.nextInt(numXTiles);
212: Point point = new Point(x, y);
213: while (!randomMap.contains(point)) {
214: x = random.nextInt(numXTiles);
215: y = random.nextInt(numXTiles);
216: point = new Point(x, y);
217: }
218: randomMap.remove(point);
219: return getScreenTile(x, y);
220: }
221:
222: /**
223: * @return Returns the bounds.
224: */
225: public Envelope getBounds() {
226: return bounds;
227: }
228:
229: /**
230: * @param bounds The bounds to set.
231: */
232: public void setBounds(Envelope bounds) {
233: this .bounds = bounds;
234: reset();
235: }
236:
237: /**
238: * @return Returns the tileSize.
239: */
240: public Dimension2D getTileSize() {
241: return tileSize;
242: }
243:
244: /**
245: * @param tileSize The tileSize to set.
246: */
247: public void setTileSize(Dimension tileSize) {
248: this .tileSize = tileSize;
249: reset();
250: }
251:
252: /**
253: * @return Returns the worldToScreen.
254: */
255: public AffineTransform getWorldToScreen() {
256: return worldToScreen;
257: }
258:
259: /**
260: * @param worldToScreen The worldToScreen to set.
261: */
262: public void setWorldToScreen(AffineTransform worldToScreen) {
263: this.worldToScreen = worldToScreen;
264: reset();
265: }
266:
267: }
|