0

I'm calculating the necessary oints for a bezier curve between to geo location points to draw the line at Google Maps V2. My problem is that it doesn't work correctly if the line has to be drawn 'over the pacific ocean', e.g. start point is in Tokyo and end point is in Vancouver. The points are calculated in the wrong direction (to the east around the earth) and not to the west. Locations points across the Atlantic ocean or Asia are calculated and drawn correct.

Where is the error in my code or my thinking?

Here is the code for calculating:

public static ArrayList<LatLng> bezier(LatLng p1, LatLng p2, double arcHeight, double skew, boolean up){
    ArrayList<LatLng> list = new ArrayList<LatLng>();
    try {
        if(p1.longitude > p2.longitude){
            LatLng tmp = p1;
            p1 = p2;
            p2 = tmp;
        }

        LatLng c = midPoint(p1, p2, 0);
        Log.v(TAG, "P1: " + p1.toString());
        Log.v(TAG, "P2: " + p2.toString());
        Log.v(TAG, "C: " + c.toString());

        double cLat = c.latitude;
        double cLon = c.longitude;


         //add skew and arcHeight to move the midPoint
        if(Math.abs(p1.longitude - p2.longitude) < 0.0001){
            if(up){
                cLon -= arcHeight;
            }else{
                cLon += arcHeight;
                cLat += skew;
            }
        }else{
            if(up){
                cLat += arcHeight;
            }else{
                cLat -= arcHeight;
                cLon += skew;
            }
        }

        list.add(p1);
         //calculating points for bezier
        double tDelta = 1.0/10;
        for (double t = 0;  t <= 1.0; t+=tDelta) {
            double oneMinusT = (1.0-t);
            double t2 = Math.pow(t, 2);
            double lon = oneMinusT * oneMinusT * p1.longitude
                        + 2 * oneMinusT * t * cLon
                        + t2 * p2.longitude;
            double lat = oneMinusT * oneMinusT * p1.latitude
                        + 2 * oneMinusT * t * cLat
                         + t2 * p2.latitude;
            Log.v(TAG, "t: " + t + "[" + lat +"|" + lon + "]");
            list.add(new LatLng(lat, lon));
        }

        list.add(p2);
    } catch (Exception e) {
        Log.e(TAG, "bezier", e);
    }
    return list;
}

Here is the output from logcat with the calculated points;

P1: lat/lng: (35.76472,140.38639)
P2: lat/lng: (49.19489,-123.17923)
C: lat/lng: (53.760800330485814,-178.27615766444313)
t: 0.0[35.76472|140.38639]
t: 0.1[39.17431615948745|80.39147522040025]
t: 0.2[42.12467250575547|27.871749947378213]
t: 0.3[44.61578903880404|-17.172785819066128]
t: 0.4[46.647665758633195|-54.7421320789327]
t: 0.5[48.22030266524291|-84.83628883222157]
t: 0.6[49.333699758633195|-107.4552560789327]
t: 0.7[49.98785703880404|-122.59903381906611]
t: 0.7[50.18277450575546|-130.2676220526218]
t: 0.8[49.918452159487444|-130.46102077959978]
t: 0.9[49.19489|-123.17923000000002]

And here is a screenshot of the map:

enter image description here

MaciejGórski
  • 22,187
  • 7
  • 70
  • 94
AlexVogel
  • 10,601
  • 10
  • 61
  • 71

1 Answers1

3

I've decided to convert the geo locations to a cartesian coordinate system and then do the calculation. This worked.

Here are the changes:

//inside bezier(...)

CartesianCoordinates cart1 = new CartesianCoordinates(p1);
CartesianCoordinates cart2 = new CartesianCoordinates(p2);
CartesianCoordinates cart3 = new CartesianCoordinates(cLat, cLon);

for (double t = 0; t <= 1.0; t += tDelta) {
    double oneMinusT = (1.0 - t);
    double t2 = Math.pow(t, 2);

    double y = oneMinusT * oneMinusT * cart1.y + 2 * t * oneMinusT * cart3.y + t2 * cart2.y;
    double x = oneMinusT * oneMinusT * cart1.x + 2 * t * oneMinusT * cart3.x + t2 * cart2.x;
    double z = oneMinusT * oneMinusT * cart1.z + 2 * t * oneMinusT * cart3.z + t2 * cart2.z;
    LatLng control = CartesianCoordinates.toLatLng(x, y, z);
    if (Config.DEBUG) 
        Log.v(TAG, "t: " + t + control.toString());
    list.add(control);
}

with CartesianCoordinates:

private static class CartesianCoordinates {
private static final int R = 6371; // approximate radius of earth
double x;
double y;
double z;

public CartesianCoordinates(LatLng p) {
    this(p.latitude, p.longitude);
}

public CartesianCoordinates(double lat, double lon) {
    double _lat = Math.toRadians(lat);
    double _lon = Math.toRadians(lon);

    x = R * Math.cos(_lat) * Math.cos(_lon);
    y = R * Math.cos(_lat) * Math.sin(_lon);
    z = R * Math.sin(_lat);
}

public static LatLng toLatLng(double x, double y, double z){
        return new LatLng(Math.toDegrees(Math.asin(z / R)), Math.toDegrees(Math.atan2(y, x)));
    }
}

Method to calculate the midpoint of two coordinates (maybe not 100% perfect mathematically correct):

private static LatLng midPoint(LatLng p1, LatLng p2) throws IllegalArgumentException{

    if(p1 == null || p2 == null)
        throw new IllegalArgumentException("two points are needed for calculation");

    double lat1;
    double lon1;
    double lat2;
    double lon2;

    //convert to radians
    lat1 = Math.toRadians(p1.latitude);
    lon1 = Math.toRadians(p1.longitude);
    lat2 = Math.toRadians(p2.latitude);
    lon2 = Math.toRadians(p2.longitude);

    double x1 = Math.cos(lat1) * Math.cos(lon1);
    double y1 = Math.cos(lat1) * Math.sin(lon1);
    double z1 = Math.sin(lat1);

    double x2 = Math.cos(lat2) * Math.cos(lon2);
    double y2 = Math.cos(lat2) * Math.sin(lon2);
    double z2 = Math.sin(lat2);

    double x = (x1 + x2)/2;
    double y = (y1 + y2)/2;
    double z = (z1 + z2)/2;

    double lon = Math.atan2(y, x);
    double hyp = Math.sqrt(x*x + y*y);

    // HACK: 0.9 and 1.1 was found by trial and error; this is probably *not* the right place to apply mid point shifting
    double lat = Math.atan2(.9*z, hyp); 
    if(lat>0) lat = Math.atan2(1.1*z, hyp);

    if(Config.DEBUG)
        Log.v(TAG, Math.toDegrees(lat) + " " + Math.toDegrees(lon));

    return new LatLng(Math.toDegrees(lat),  Math.toDegrees(lon));
}
Community
  • 1
  • 1
AlexVogel
  • 10,601
  • 10
  • 61
  • 71