Konwersja długości / szerokości geograficznej na współrzędną X / Y

Stworzyłem mapę za pomocą Google Maps API, który podkreśla wszystkie hrabstwa Minnesoty. Zasadniczo, stworzyłem wielokąty hrabstwa za pomocą zestawu współrzędnych długości/szerokości geograficznej. Oto zrzut ekranu Wygenerowanej Mapy: -

Tutaj wpisz opis obrazka

Jednym z wymagań użytkownika jest możliwość posiadania podobnej Mapy jako obrazu, aby móc osadzić ją w swoich slajdach PowerPoint/keynote. Nie mogłem znaleźć żadnego użytecznego interfejsu API Map Google, który pozwala mi zapisać moją niestandardową mapę w taki sposób, w jaki jest (jeśli znasz sposób, daj mi znać), więc pomyślałem, że powinienem narysować go za pomocą Graphics2D w Javie.

Po przeczytaniu o formułach do konwersji długości / szerokości geograficznej na współrzędną X / Y, kończę z następującym kodem: -

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(...);
    ...
}

Wygenerowana mapa jest podobna do tej generowanej przez Google Maps API przy użyciu tego samego zestawu długości / szerokości geograficznej. Jednak wydaje się trochę przechylony i wygląda trochę off, i nie jestem pewien, jak to naprawić.

Tutaj wpisz opis obrazka

Jak zrobić kształt powiatów wyglądać jak ten wygenerowany przez Google Maps API powyżej?

Wielkie dzięki.

ROZWIĄZANIE KOŃCOWE

W końcu znalazłem rozwiązanie dzięki @QuantumMechanic i @Anon.

Projekcja Mercatora naprawdę działa. Używam Java Map Projection Library do wykonywania obliczeń dla projekcji Mercatora.
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 + d.x) * 6000);
        int magnifiedY = (int) Math.round((10 + d.y) * 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;
}

...

Używając Wygenerowanej współrzędnej XY, Mapa wydaje się odwrócona, a to dlatego, że wierzę, że graphics2D ' s 0,0 zaczyna się w lewym górnym rogu. Więc muszę odwrócić Y, odejmując wartość od wysokości obrazu, coś w tym stylu: -

...

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);
}

...

Oto wygenerowana Mapa: -

Tutaj wpisz opis obrazka

TO JEST IDEALNE!

Aktualizacja 01-25-2013

Oto kod do tworzenia mapy obrazu na podstawie szerokości i wysokości (w pikselach). W tym przypadku nie polegam na Bibliotece projektu Java Map, zamiast tego wyodrębniłem odpowiednią formułę i osadziłem ją w moim kod. Daje to większą kontrolę nad generowaniem map w porównaniu do powyższego przykładu kodu, który opiera się na dowolnej wartości skalowania (powyższy przykład wykorzystuje 6000).

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 = 600px, Image height = 600px, Image padding = 50px

Tutaj wpisz opis obrazka

RESULT: Image width= 300px, Image height = 500px, Image padding = 50px

Tutaj wpisz opis obrazka

Author: limc, 2011-05-12

3 answers

Duży problem z kreśleniem map polega na tym, że sferyczna powierzchnia ziemi nie może być wygodnie przekształcona w płaską reprezentację. Istnieje kilka różnych prognoz, które próbują rozwiązać ten problem.

Mercator jest jednym z najprostszych: zakłada, że linie o równej szerokości geograficznej są równoległymi poziomami, podczas gdy linie o równej długości geograficznej są równoległymi pionami. Obowiązuje to dla szerokości geograficznej (1 stopień szerokości geograficznej w przybliżeniu równa się 111 km bez względu na to, gdzie jesteś), ale nie dotyczy długości geograficznej (odległość powierzchniowa stopnia długości geograficznej jest proporcjonalna do cosinusa szerokości geograficznej).

Jednak, tak długo, jak jesteś poniżej około 45 stopni (które jest większość Minnesoty), projekcja Merkatora działa bardzo dobrze i tworzy formy, które większość ludzi rozpozna ze swoich map szkolnych. Jest to bardzo proste: po prostu traktuj punkty jako współrzędne bezwzględne i skaluj do dowolnej przestrzeni, w której je rysujesz. Nie trzeba trygować.

 12
Author: Anon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-12 19:16:47

Pamiętaj, że wygląd mapy jest funkcją projekcji używaną do renderowania mapy. Google Maps wydaje się używać projekcji Mercatora (lub czegoś bardzo podobnego do niego). Z jaką projekcją odpowiada Twój algorytm? Jeśli chcesz, aby Twoja reprezentacja 2D wyglądała tak samo jak Google, musisz użyć identycznej projekcji.

 6
Author: QuantumMechanic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-12 19:10:41

Aby przekonwertować lat/lon / alt (lat w stopniach północy, lon w stopniach Wschodu, alt w metrach) na ziemi wyśrodkowane stałe współrzędne (x, y, z), wykonaj następujące czynności:

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);
 5
Author: toadaly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-12 19:20:01