|
|
Raben Systems
Inc. |
| Software Development Services
|
Shown below is code that provides an abstract skeletal implementation of the
ProjectionInterface. The purpose of doing this is to implement methods that
are common to most of the projections and thus save some work. At the same time
the constraints that abstract classes impose is avoided as the developer has
the choice of whether to implement the interface completely or to simply extend
the abstract class. Most of the methods are simply setters and getters common
to most projection types. It is only necessary to implement four methods for
a particular projection -- getLocationForCoordinate, getCoordinateForLocation,
getOverlayGridPath, and getProjectionName.
1 /*
2 * AbstractMapProjection.java
3 *
4 * Copyright (c) 2002, 2003, Raben Systems, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * Neither the name of Raben Systems, Inc. nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 *
33 * Created on June 7, 2002, 11:16 AM
34 */
35
36 package com.raben.projection.map;
37 import java.awt.geom.Point2D;
38 import java.awt.geom.Rectangle2D;
39 import java.awt.geom.PathIterator;
40 import java.awt.Shape;
41 import java.awt.geom.AffineTransform;
42 import java.awt.Graphics2D;
43 import java.awt.geom.GeneralPath;
44 import java.awt.Font;
45 import java.awt.font.FontRenderContext;
46 import java.awt.font.TextLayout;
47 import java.util.ArrayList;
48 import java.util.HashMap;
49
50 /***
51 * Skeletal implementation of MapProjection interface
52 * @author Vern Raben
53 * @version $Revision: 1.1 $ $Date: 2003/10/10 15:42:54 $
54 * Copyright (c) Raben Systems, Inc., 2002, 2003
55 * All rights reserved
56 */
57 public abstract class AbstractMapProjection implements MapProjection {
58
59 /*** radius of the map */
60 private double radius = 1.0;
61
62 /*** Center screen coordinate of the map */
63 private final Point2D.Double centerPoint = new Point2D.Double(0.0, 0.0);
64
65 /*** Center coordinate longitude and latitude */
66 private final Point2D.Double centerCoordinate
67 = new Point2D.Double(0.0, 0.0);
68
69 /*** Math sin of center latitude */
70 private double sinCenterLatitude = 0.0;
71
72 /*** Math cos of center coordinate */
73 private double cosCenterLatitude = 1.0;
74
75 /*** Overlay grid increment along grid being drawn */
76 private double overlayGridIncrement = Math.toRadians(2.0);
77
78 /*** Overlay grid longitude increment */
79 private double overlayGridLongitudeIncrement = Math.toRadians(10.0);
80
81 /*** Overlay grid latitude increment */
82 private double overlayGridLatitudeIncrement = Math.toRadians(10.0);
83
84 /*** The text overlay path */
85 private final GeneralPath overlayTextPath = new GeneralPath();
86
87 /*** ArrayList of overlay text */
88 private final ArrayList overlayTextList = new ArrayList();
89
90 /*** latitude range in radians */
91 private double latitudeRange = Math.PI;
92
93 /*** HashMap containing coordinate list overlays */
94 private HashMap overlayMap = new HashMap();
95
96 /*** Ellipsoid eccentricity */
97 private double eccentricity = 1.0;
98
99 /*** Holds value of property textRadius. */
100 private double textRadius;
101
102 /*** Holds value of property mirror. */
103 private boolean textMirrored = false;
104
105 /*** Holds value of property inverted. */
106 private boolean textInverted = false;
107
108 /*** Get screen coordinates of center of map
109 * @return Value of property centerPoint.
110 */
111 public java.awt.geom.Point2D getCenterPoint() {
112 return new Point2D.Double(centerPoint.x, centerPoint.y);
113 }
114
115
116 /***
117 * Get latitude range in radians
118 * @return The latitude range
119 */
120 public double getLatitudeRange() {
121 return latitudeRange;
122 }
123
124 /***
125 * Set centerPoint screen coordinate
126 * @param centerPoint Point2D.Double Screen location of the center of map
127 */
128 public void setCenterPoint(java.awt.geom.Point2D centerPoint) {
129 this.centerPoint.setLocation(centerPoint.getX(), centerPoint.getY());
130 }
131
132 /***
133 * Get sin of center latitude
134 * @return the sin value
135 */
136 protected double getSinLatCenter() {
137 return sinCenterLatitude;
138 }
139
140 /***
141 * Get cos of center latitude
142 * @return the cos value
143 */
144 protected double getCosLatCenter() {
145 return cosCenterLatitude;
146 }
147
148 /*** Get longitude and latitude of center coordinate in radians
149 * @return The center coordinate
150 */
151 public Point2D getCenterCoordinate() {
152 return new Point2D.Double(centerCoordinate.getX(),
153 centerCoordinate.getY());
154 }
155
156 /*** Set longitude and latitude of the center coordinate in radians
157 * @param centerCoordinate New value of centerCoordinate.
158 */
159 public void setCenterCoordinate(java.awt.geom.Point2D centerCoordinate) {
160 this.centerCoordinate.setLocation(centerCoordinate.getX(),
161 centerCoordinate.getY());
162 normalizeCoordinate(this.centerCoordinate);
163 sinCenterLatitude = Math.sin(this.centerCoordinate.getY());
164 cosCenterLatitude = Math.cos(this.centerCoordinate.getY());
165 }
166
167 /*** Get radius of the map
168 * @return Value of property radius.
169 */
170 public double getRadius() {
171 return radius;
172 }
173
174 /***
175 * Set radius of the map
176 * @param radius New value of property radius.
177 */
178 public void setRadius(double radius) {
179 this.radius = radius;
180 }
181
182 /***
183 * Get overlay grid increment in radians along the grid line
184 * @return double Grid increment in radians along grid line
185 */
186 public double getOverlayGridIncrement() {
187 return overlayGridIncrement;
188 }
189
190 /***
191 * Set latitude range in radians (example -PI/4 to +PI/4 = PI/2)
192 * @param latitudeRange double in radians
193 */
194 public void setLatitudeRange(double latitudeRange) {
195 this.latitudeRange = latitudeRange;
196 }
197
198 /***
199 * Set overlay grid increment along the grid line
200 * @param gridIncrement in radians
201 */
202 public void setOverlayGridIncrement(double gridIncrement) {
203 if (gridIncrement != 0.0) {
204 this.overlayGridIncrement = gridIncrement;
205 }
206 }
207
208
209 /***
210 * Get overlay grid longitude increment in radians
211 * @return double Amount longitude is incremented when drawing grid
212 */
213 public double getOverlayGridLongitudeIncrement() {
214 return overlayGridLongitudeIncrement;
215 }
216
217 /***
218 * Set overlay grid longitude increment in radians
219 * @param longitudeIncrement Amount longitude is incremented
220 * when drawing grid
221 */
222 public void setOverlayGridLongitudeIncrement(double longitudeIncrement) {
223 if (longitudeIncrement != 0.0) {
224 this.overlayGridLongitudeIncrement = longitudeIncrement;
225 }
226 }
227
228 /***
229 * Set overlay grid latitude increment in radians
230 * @param latitudeIncrement Amount latitude is incremented when drawing grid
231 */
232 public void setOverlayGridLatitudeIncrement(double latitudeIncrement) {
233 this.overlayGridLatitudeIncrement = latitudeIncrement;
234 }
235
236 /***
237 * Get overlay grid latitude increment in radians
238 * @return double Amount latitude is incremented when drawing grid
239 */
240 public double getOverlayGridLatitudeIncrement() {
241 return overlayGridLatitudeIncrement;
242 }
243
244 /***
245 * Add overlay text
246 * @param coordTxt Add text to overlay for a coordinate
247 */
248 public void addOverlayText(CoordinateText coordTxt) {
249 overlayTextList.add(coordTxt);
250 }
251
252 /***
253 * Get shape for text string using affine transform and font specified in
254 * Graphics resource
255 * @param g Graphics resource (font to be used should be set)
256 * @param at AffineTransform to translate text to desired screen location
257 * @param str String to be displayed at coordinate specified
258 * @return The text shape to draw or paint
259 */
260 private Shape getShapeForText(Graphics2D g, AffineTransform at,
261 String str) {
262 Font font = g.getFont();
263 FontRenderContext frc = g.getFontRenderContext();
264 TextLayout layout = new TextLayout(str, font, frc);
265 Rectangle2D bounds = layout.getBounds();
266 // Center the text
267 at.translate(-0.5 * bounds.getWidth(), 0.5 * bounds.getHeight());
268 return layout.getOutline(at);
269 }
270
271 /*** Get text overlay for the map as a shape
272 * @param g2D Graphics context
273 * @return Shape to draw/paint text overlay
274 */
275 public GeneralPath getTextPath(Graphics2D g2D) {
276 overlayTextPath.reset();
277 double vScale = -1.0;
278 double hScale = 1.0;
279
280 if (isTextInverted()) {
281 vScale = 1.0;
282 } else {
283 vScale = -1.0;
284 }
285
286 if (isTextMirrored()) {
287 hScale = -1.0;
288 } else {
289 hScale = 1.0;
290 }
291
292 for (int i = 0; i < overlayTextList.size(); i++) {
293 AffineTransform at = new AffineTransform();
294 CoordinateText ct = (CoordinateText) overlayTextList.get(i);
295 Point2D location = getLocationForCoordinate(ct.getCoordinate());
296
297 if ((!Double.isNaN(location.getX()))
298 && (!Double.isNaN(location.getY()))) {
299 at.translate(location.getX(), location.getY());
300 at.translate(ct.getDistanceX(), ct.getDistanceY());
301 at.scale(hScale, vScale);
302 Shape textShape = getShapeForText(g2D, at, ct.getText());
303 overlayTextPath.append(textShape, false);
304 }
305
306 }
307
308 return overlayTextPath;
309 }
310
311 /***
312 * Get overlay path for specified name
313 * @param name String
314 * @return GeneralPath
315 */
316 public GeneralPath getOverlayPath(String name) {
317 GeneralPath generalPath = new GeneralPath();
318 CoordinateList coordinateList = (CoordinateList) overlayMap.get(name);
319
320 for (int i = 0; i < coordinateList.size(); i++) {
321 Point2D coordinate = coordinateList.getCoordinate(i);
322 Point2D location = getLocationForCoordinate(coordinate);
323 int segment = coordinateList.getSegment(i);
324
325 if (!Double.isNaN(location.getX())) {
326 switch(segment) {
327 case PathIterator.SEG_MOVETO:
328 generalPath.moveTo((float) location.getX(),
329 -(float) location.getY());
330 break;
331 case PathIterator.SEG_LINETO:
332 generalPath.lineTo((float) location.getX(),
333 -(float) location.getY());
334 break;
335
336 }
337 }
338 }
339
340 return generalPath;
341 }
342
343
344 /***
345 * Normalize coordinate so that longitude is in the range -PI to +PI and
346 * latitude is in the range -PI_OVER_2 to +PI_OVER_2;
347 * @param coordinate in radians
348 */
349 public void normalizeCoordinate(Point2D coordinate) {
350
351 coordinate.setLocation(normalizeLongitude(coordinate.getX()),
352 normalizeLatitude(coordinate.getY()));
353 }
354
355 /***
356 * Normalize latitude so that its in range -PI to +PI
357 * @param latitude double (in radians)
358 * @return double Normalized latitude in radians
359 */
360 public double normalizeLatitude(double latitude) {
361
362 if (!Double.isNaN(latitude)) {
363
364 while (latitude > Math.PI) {
365 latitude -= MapProjectionConstants.PI_TIMES_2;
366 }
367
368 while (latitude <= -Math.PI) {
369 latitude += MapProjectionConstants.PI_TIMES_2;
370 }
371
372 }
373
374 return latitude;
375 }
376
377 /***
378 * Normalize longitude so that its in range -PI to +PI
379 * @param longitude double (in radians)
380 * @return double Normalize longitude in radians
381 */
382 public double normalizeLongitude(double longitude) {
383
384 if (!Double.isNaN(longitude)) {
385
386 while (longitude > Math.PI) {
387 longitude -= MapProjectionConstants.PI_TIMES_2;
388 }
389
390 while (longitude <= (-Math.PI)) {
391 longitude += MapProjectionConstants.PI_TIMES_2;
392 }
393 }
394
395 return longitude;
396 }
397
398
399
400 /***
401 * Convert coordinate in radians to degrees
402 * @param radians Point2D coordinate in radians
403 * @return Point2D coordinate in degrees
404 */
405 public Point2D toDegrees(Point2D radians) {
406 return new Point2D.Double(Math.toDegrees(radians.getX()),
407 Math.toDegrees(radians.getY()));
408 }
409
410 /***
411 * Convert coordinate in degrees to radians
412 * @param degrees Point2D in degrees
413 * @return Point2D Coordinate in radians
414 */
415 public Point2D toRadians(Point2D degrees) {
416 return new Point2D.Double(Math.toRadians(degrees.getX()),
417 Math.toRadians(degrees.getY()));
418 }
419
420 /***
421 * Get string representation of field values
422 * @return The string representation
423 */
424 public String toString() {
425 StringBuffer buf = new StringBuffer(getClass().getName());
426 buf.append("[centerPoint = ");
427 buf.append(getCenterPoint());
428 buf.append(", radius = ");
429 buf.append(getRadius());
430 buf.append(", centerCoordinate = ");
431 buf.append(toDegrees(getCenterCoordinate()));
432 buf.append(", overlayGridIncrement = ");
433 buf.append(Math.toDegrees(getOverlayGridIncrement()));
434 buf.append(", overlayLongitudeIncrement = ");
435 buf.append(Math.toDegrees(getOverlayGridLongitudeIncrement()));
436 buf.append(", overlayLatitudeIncrement = ");
437 buf.append(Math.toDegrees(getOverlayGridLatitudeIncrement()));
438 buf.append(", latitudeRange = ");
439 buf.append(Math.toDegrees(getLatitudeRange()));
440 buf.append(", textMirrored = ");
441 buf.append(isTextMirrored());
442 buf.append(", textInverted = ");
443 buf.append(isTextInverted());
444 buf.append("]");
445 return buf.toString();
446 }
447
448
449
450 /***
451 * Set ellipsoid eccentricity
452 * @param eccentricity double
453 */
454 public void setEccentricity(double eccentricity) {
455 this.eccentricity = eccentricity;
456 }
457
458 /***
459 * Get ellipsoid eccentricity
460 * @return double
461 */
462 public double getEccentricity() {
463 return eccentricity;
464 }
465
466
467 /*** Getter for property textRadius.
468 * @return Value of property textRadius.
469 */
470 public double getTextRadius() {
471 return this.textRadius;
472 }
473
474 /*** Setter for property textRadius.
475 * @param textRadius New value of property textRadius.
476 */
477 public void setTextRadius(double textRadius) {
478 this.textRadius = textRadius;
479 }
480
481 /***
482 * Set coordinate text to be displayed
483 * @param coordinateTextList containing coordinate text to be overlayed
484 */
485 public void setCoordinateTextList(CoordinateTextList coordinateTextList) {
486 // Clear all coordinate text
487
488 if (coordinateTextList != null) {
489 this.overlayTextList.clear();
490
491 for (int i = 0; i < coordinateTextList.size(); i++) {
492 CoordinateText coordText
493 = coordinateTextList.getCoordinateText(i);
494 Point2D coord = coordText.getCoordinate();
495 coordText2 = new CoordinateText(coord,
496 coordText.getText(), coordText.getDistanceX(),
497 coordText.getDistanceY());
498 this.overlayTextList.add(coordText2);
499 }
500 }
501 }
502
503 /***
504 * Returns whether text is mirrored (flipped horizontally)
505 * @return True if text is mirrored
506 */
507 public boolean isTextMirrored() {
508 return this.textMirrored;
509 }
510
511 /*** Set whether projection is mirrored by display routine
512 * affects text display only
513 * @param textMirrored True if text should be mirrored
514 */
515 public void setTextMirrored(boolean textMirrored) {
516 this.textMirrored = textMirrored;
517 }
518
519 /*** Returns whther or not text is inverted (flipped vertically)
520 * @return True if text is inverted
521 */
522 public boolean isTextInverted() {
523 return this.textInverted;
524 }
525
526 /*** Set whether or not text is inverted (flipped vertically)
527 * @param textInverted True if text is to be inverted
528 */
529 public void setTextInverted(boolean textInverted) {
530 this.textInverted = textInverted;
531 }
532
533 }
534
535

