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

Back to MapProjection interfaceGo to article index Next the equidistant cylindrical projection