Convert Longitude / Latitude to X / Y Coordinate

I created a map using the Google Maps API that selects all Minnesota counties. Basically, I created graph polygons using a set of longitude / latitude coordinates. Here's a screenshot of the generated map: -

enter image description here

One of the user's requirements is the ability to have a similar map as an image so that they can embed it in PowerPoint / keynote slides. I could not find a useful Google Maps API that allows me to save my custom map as it is (if you know the way, let me know), so I suppose I should just draw it using Graphics2D in Java.

After reading the formulas for converting longitude / latitude to X / Y coordinate, I get the following code: -

private static final int EARTH_RADIUS = 6371; private static final double FOCAL_LENGTH = 500; ... BufferedImage bi = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics2D g = bi.createGraphics(); for (Coordinate coordinate : coordinates) { double latitude = Double.valueOf(coordinate.getLatitude()); double longitude = Double.valueOf(coordinate.getLongitude()); latitude = latitude * Math.PI / 180; longitude = longitude * Math.PI / 180; double x = EARTH_RADIUS * Math.sin(latitude) * Math.cos(longitude); double y = EARTH_RADIUS * Math.sin(latitude) * Math.sin(longitude); double z = EARTH_RADIUS * Math.cos(latitude); double projectedX = x * FOCAL_LENGTH / (FOCAL_LENGTH + z); double projectedY = y * FOCAL_LENGTH / (FOCAL_LENGTH + z); // scale the map bigger int magnifiedX = (int) Math.round(projectedX * 5); int magnifiedY = (int) Math.round(projectedY * 5); ... g.drawPolygon(...); ... } 

The generated map is similar to the one generated by the Google Maps API using the same set of longitude / latitude. However, it is a little tilted, and it looks a bit, and I'm not sure how to fix it.

enter image description here

How do I make a county shape look like the one created by the Google Maps API above?

Thank you very much.

FINAL DECISION

I finally found a solution thanks to @QuantumMechanic and @Anon.

Mercator's forecast really does the trick. I use the Java Map Projection Library to do the calculation for the Mercator projection.

 private static final int IMAGE_WIDTH = 1000; private static final int IMAGE_HEIGHT = 1000; private static final int IMAGE_PADDING = 50; ... private List<Point2D.Double> convertToXY(List<Coordinate> coordinates) { List<Point2D.Double> xys = new ArrayList<Point2D.Double>(); MercatorProjection projection = new MercatorProjection(); for (Coordinate coordinate : coordinates) { double latitude = Double.valueOf(coordinate.getLatitude()); double longitude = Double.valueOf(coordinate.getLongitude()); // convert to radian latitude = latitude * Math.PI / 180; longitude = longitude * Math.PI / 180; Point2D.Double d = projection.project(longitude, latitude, new Point2D.Double()); // shift by 10 to remove negative Xs and Ys // scaling by 6000 to make the map bigger int magnifiedX = (int) Math.round((10 + dx) * 6000); int magnifiedY = (int) Math.round((10 + dy) * 6000); minX = (minX == -1) ? magnifiedX : Math.min(minX, magnifiedX); minY = (minY == -1) ? magnifiedY : Math.min(minY, magnifiedY); xys.add(new Point2D.Double(magnifiedX, magnifiedY)); } return xys; } ... 

Using the generated XY coordinate, the map seems to be inverted, and because I believe that graphics2D 0,0 starts in the upper left corner. So, I need to invert Y by subtracting the value from the image height, something like this: -

 ... Polygon polygon = new Polygon(); for (Point2D.Double point : xys) { int adjustedX = (int) (IMAGE_PADDING + (point.getX() - minX)); // need to invert the Y since 0,0 starts at top left int adjustedY = (int) (IMAGE_HEIGHT - IMAGE_PADDING - (point.getY() - minY)); polygon.addPoint(adjustedX, adjustedY); } ... 

Here's the generated mapping: -

enter image description here

DONE!

UPDATE 01-25-2013

Here is the code to create an image map based on the width and height (in pixels). In this case, I do not rely on the Java Map Project library, instead I extracted the appropriate formula and embedded it in my code. This gives you more control over map creation compared to the above code example, which depends on an arbitrary zoom value (6000 is used in the example above).

 public class MapService { // CHANGE THIS: the output path of the image to be created private static final String IMAGE_FILE_PATH = "/some/user/path/map.png"; // CHANGE THIS: image width in pixel private static final int IMAGE_WIDTH_IN_PX = 300; // CHANGE THIS: image height in pixel private static final int IMAGE_HEIGHT_IN_PX = 500; // CHANGE THIS: minimum padding in pixel private static final int MINIMUM_IMAGE_PADDING_IN_PX = 50; // formula for quarter PI private final static double QUARTERPI = Math.PI / 4.0; // some service that provides the county boundaries data in longitude and latitude private CountyService countyService; public void run() throws Exception { // configuring the buffered image and graphics to draw the map BufferedImage bufferedImage = new BufferedImage(IMAGE_WIDTH_IN_PX, IMAGE_HEIGHT_IN_PX, BufferedImage.TYPE_INT_RGB); Graphics2D g = bufferedImage.createGraphics(); Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>(); map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); RenderingHints renderHints = new RenderingHints(map); g.setRenderingHints(renderHints); // min and max coordinates, used in the computation below Point2D.Double minXY = new Point2D.Double(-1, -1); Point2D.Double maxXY = new Point2D.Double(-1, -1); // a list of counties where each county contains a list of coordinates that form the county boundary Collection<Collection<Point2D.Double>> countyBoundaries = new ArrayList<Collection<Point2D.Double>>(); // for every county, convert the longitude/latitude to X/Y using Mercator projection formula for (County county : countyService.getAllCounties()) { Collection<Point2D.Double> lonLat = new ArrayList<Point2D.Double>(); for (CountyBoundary countyBoundary : county.getCountyBoundaries()) { // convert to radian double longitude = countyBoundary.getLongitude() * Math.PI / 180; double latitude = countyBoundary.getLatitude() * Math.PI / 180; Point2D.Double xy = new Point2D.Double(); xy.x = longitude; xy.y = Math.log(Math.tan(QUARTERPI + 0.5 * latitude)); // The reason we need to determine the min X and Y values is because in order to draw the map, // we need to offset the position so that there will be no negative X and Y values minXY.x = (minXY.x == -1) ? xy.x : Math.min(minXY.x, xy.x); minXY.y = (minXY.y == -1) ? xy.y : Math.min(minXY.y, xy.y); lonLat.add(xy); } countyBoundaries.add(lonLat); } // readjust coordinate to ensure there are no negative values for (Collection<Point2D.Double> points : countyBoundaries) { for (Point2D.Double point : points) { point.x = point.x - minXY.x; point.y = point.y - minXY.y; // now, we need to keep track the max X and Y values maxXY.x = (maxXY.x == -1) ? point.x : Math.max(maxXY.x, point.x); maxXY.y = (maxXY.y == -1) ? point.y : Math.max(maxXY.y, point.y); } } int paddingBothSides = MINIMUM_IMAGE_PADDING_IN_PX * 2; // the actual drawing space for the map on the image int mapWidth = IMAGE_WIDTH_IN_PX - paddingBothSides; int mapHeight = IMAGE_HEIGHT_IN_PX - paddingBothSides; // determine the width and height ratio because we need to magnify the map to fit into the given image dimension double mapWidthRatio = mapWidth / maxXY.x; double mapHeightRatio = mapHeight / maxXY.y; // using different ratios for width and height will cause the map to be stretched. So, we have to determine // the global ratio that will perfectly fit into the given image dimension double globalRatio = Math.min(mapWidthRatio, mapHeightRatio); // now we need to readjust the padding to ensure the map is always drawn on the center of the given image dimension double heightPadding = (IMAGE_HEIGHT_IN_PX - (globalRatio * maxXY.y)) / 2; double widthPadding = (IMAGE_WIDTH_IN_PX - (globalRatio * maxXY.x)) / 2; // for each country, draw the boundary using polygon for (Collection<Point2D.Double> points : countyBoundaries) { Polygon polygon = new Polygon(); for (Point2D.Double point : points) { int adjustedX = (int) (widthPadding + (point.getX() * globalRatio)); // need to invert the Y since 0,0 starts at top left int adjustedY = (int) (IMAGE_HEIGHT_IN_PX - heightPadding - (point.getY() * globalRatio)); polygon.addPoint(adjustedX, adjustedY); } g.drawPolygon(polygon); } // create the image file ImageIO.write(bufferedImage, "PNG", new File(IMAGE_FILE_PATH)); } } 

RESULT: image width = 600 pixels, image height = 600 pixels, image fill = 50 pixels

enter image description here

RESULT: image width = 300 pixels, image height = 500 pixels, image fill = 50 pixels

enter image description here

+36
java google-maps latitude-longitude coordinate-transformation cartesian
May 12 '11 at 19:06
source share
3 answers

The big problem with mapping is that the spherical surface of the Earth cannot be conveniently converted to a flat representation. There are many different predictions that try to solve this problem.

Mercator is one of the simplest: it is assumed that lines of equal latitude are parallel horizontals, and lines of equal longitude are parallel verticals. This is true for latitude (1 degree latitude is approximately 111 km no matter where you are), but not valid for longitude (the surface distance of a degree of longitude is proportional to the cosine of latitutude).

However, while you are below 45 degrees (most of them are in Minnesota), the Mercator projection works very well and creates forms that most people recognize from their school's school maps. And it is very simple: just treat the points as absolute coordinates and scale any place where you insert them. No triggers required.

+11
May 12 '11 at 19:16
source share

Remember that the map display method is a projection function used to display the map. Google Maps seems to use the Mercator projection (or something very similar to it). Which projection matches your algorithm? If you want your 2D view to look just like Google, you need to use an identical projection.

+6
May 12 '11 at 19:10
source share

To convert lat / lon / alt (lat in degrees north, lon in degrees east, in meters), to fixed coordinates with coordinates (x, y, z) centered in the center, do the following:

 double Re = 6378137; double Rp = 6356752.31424518; double latrad = lat/180.0*Math.PI; double lonrad = lon/180.0*Math.PI; double coslat = Math.cos(latrad); double sinlat = Math.sin(latrad); double coslon = Math.cos(lonrad); double sinlon = Math.sin(lonrad); double term1 = (Re*Re*coslat)/ Math.sqrt(Re*Re*coslat*coslat + Rp*Rp*sinlat*sinlat); double term2 = alt*coslat + term1; double x=coslon*term2; double y=sinlon*term2; double z = alt*sinlat + (Rp*Rp*sinlat)/ Math.sqrt(Re*Re*coslat*coslat + Rp*Rp*sinlat*sinlat); 
+4
May 12 '11 at 19:20
source share



All Articles