Part II - Equidistant Cylindrical Projection

Oct 10, 2003 -- Developing Map Projections with Java. In the second article of a multi-part series, one of the simplest map projections, the equidistant cylindrical projection, is developed. VR

The equidistant cylindrical projection is the simplest of the projections to implement. It probably originated with Eratosthenes who lived from about 275 - 195 B. C.

The equations for the equidistant cylindrical projection are simply:

The inverse equations are:

 

 Equidistant cylindrical map of the Earth
Equidistant cylindrical map projection of Earth from NASA's Blue Marble web site.

The code for the methods getCoordinateForLocation and getLocationForCoordinate shown below are straight forward implementations of those equations.

One of the challenges faced by mapping software is how to efficiently overlay a grid consisting of thousands of points. It turns out that computing thousands of coordinates is not a problem. This can be accoumplished in a few milli-seconds with current Java compilers and processors. Unfortuantely, rendering those points can take 100 times longer than actually computing them.

After timing various rendering operations, we found the most efficient was the java.awt.GeneralPath. GeneralPath can render thousands of points far faster than by using large numbers of calls to drawLine or by filling ellipses. The method getOverlayGridPath creates the meridians and parallels by calls to GeneralPaths "moveTo" and "lineTo" statement.

 


1 /* 2 * EquidistantCylindricalMapProjection.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 July 2, 2002, 3:45 PM 34 */ 35 36 package com.raben.projection.map; 37 import java.awt.geom.Point2D; 38 import java.awt.geom.GeneralPath; 39 40 /*** 41 * Implementation ProjectionInterface for Equidistant Cylindrical map projection 42 * @author Vern Raben 43 * @version $Revision: 1.7 $ $Date: 2002/09/19 00:16:39 $ 44 */ 45 public class EquidistantCylindricalMapProjection 46 extends AbstractMapProjection { 47 48 /*** Creates new CylindricalEquidistantMapProjection */ 49 public EquidistantCylindricalMapProjection() { 50 setCenterCoordinate(new Point2D.Double(Math.PI, 0.0)); 51 } 52 53 /*** 54 * Get coordinate for a given location in radians 55 * @param loc Point2D Screen location 56 * @return Point2D Coordinate of the point in radians 57 */ 58 public Point2D getCoordinateForLocation(Point2D loc) { 59 Point2D.Double coordinate = new Point2D.Double(Double.NaN, 60 Double.NaN); 61 Point2D centerCoordinate = getCenterCoordinate(); 62 Point2D centerPoint = getCenterPoint(); 63 double radius = getRadius(); 64 65 if (!Double.isNaN(loc.getX())) { 66 double x = loc.getX() - centerPoint.getX(); 67 double y = loc.getY() - centerPoint.getY(); 68 double latitude = y / radius; 69 double longitude = centerCoordinate.getX() + (x / radius); 70 coordinate.setLocation(longitude, latitude); 71 normalizeCoordinate(coordinate); 72 } 73 74 return coordinate; 75 } 76 77 /*** 78 * Get location for specified coordinate in radians 79 * @param coordinate Point2D longitude and latitude of coordinate in radians 80 * @return Point2D Screen location of the coordinate 81 */ 82 public Point2D getLocationForCoordinate(Point2D coordinate) { 83 Point2D.Double location = new Point2D.Double(Double.NaN, Double.NaN); 84 Point2D centerPoint = getCenterPoint(); 85 Point2D centerCoordinate = getCenterCoordinate(); 86 double radius = getRadius(); 87 88 if (!Double.isNaN(coordinate.getX())) { 89 double lonDiff = normalizeLongitude(coordinate.getX() 90 - centerCoordinate.getX()); 91 double x = centerPoint.getX() + (radius * lonDiff); 92 double y = radius * coordinate.getY(); 93 location.setLocation(x, y); 94 } 95 96 return location; 97 } 98 99 /*** 100 * Get overlay grid for map 101 * @return mapOverlay GeneneralPath 102 */ 103 public GeneralPath getOverlayGridPath() { 104 GeneralPath overlayGridPath = new GeneralPath(); 105 double radius = getRadius(); 106 Point2D centerCoordinate = getCenterCoordinate(); 107 Point2D centerPoint = getCenterPoint(); 108 float mark = (float) (radius / 360.0); 109 double latLim = getLatitudeRange() / 2.0; 110 double overlayGridLatitudeIncrement = getOverlayGridLatitudeIncrement(); 111 double overlayGridIncrement = getOverlayGridIncrement(); 112 double overlayGridLongitudeIncrement 113 = getOverlayGridLongitudeIncrement(); 114 115 // Create latitude lines 116 for (double lat = -latLim; lat <= latLim; 117 lat += overlayGridLatitudeIncrement) { 118 float y = (float) (radius * lat); 119 120 for (double lon = -Math.PI; lon <= Math.PI; 121 lon += overlayGridIncrement) { 122 double lonDiff = normalizeLongitude(lon 123 - centerCoordinate.getX()); 124 float x = (float) (centerPoint.getX() + (radius * lonDiff)); 125 overlayGridPath.moveTo(x - mark, y); 126 overlayGridPath.lineTo(x + mark, y); 127 overlayGridPath.moveTo(x, y - mark); 128 overlayGridPath.lineTo(x, y + mark); 129 } 130 } 131 132 // Create longitude lines 133 for (double lon = -Math.PI; lon <= Math.PI; 134 lon += overlayGridLongitudeIncrement) { 135 double lonDiff = normalizeLongitude(lon - centerCoordinate.getX()); 136 float x = (float) (centerPoint.getX() + (radius * lonDiff)); 137 138 for (double lat = -latLim; lat <= latLim; 139 lat += overlayGridIncrement) { 140 float y = (float) (radius * lat); 141 overlayGridPath.moveTo(x - mark, y); 142 overlayGridPath.lineTo(x + mark, y); 143 overlayGridPath.moveTo(x, y - mark); 144 overlayGridPath.lineTo(x, y + mark); 145 } 146 } 147 148 return overlayGridPath; 149 } 150 151 /*** 152 * Get name of the projection 153 * @return ProjectionName 154 */ 155 public ProjectionName getProjectionName() { 156 return ProjectionName.EQUIDISTANT_CYLINDRICAL; 157 } 158 159 }

Back to skeleton implementationGo to article index