web-dev-qa-db-ja.com

緯度と経度の座標を時計回りに並べられた四辺形に並べ替えます

問題

ユーザーは、最大4つの緯度と経度の座標を任意の順序で指定できます。彼らはグーグルマップでそうします。 GoogleのPolygon AP​​I(v3)を使用して、選択した座標は、4つの座標の間の選択した領域を強調表示する必要があります。

質問

緯度と経度の座標の配列を(反)時計回りにどのように並べ替えますか?

ソリューションと検索

StackOverflowの質問

関連サイト

既知のアルゴリズム

  • グラハムスキャン(複雑すぎる)
  • ジャービスマーチアルゴリズム(Nポイントを処理)
  • 再帰的凸包(ポイントを削除します)

コード

これが私がこれまでに持っているものです:

// Ensures the markers are sorted: NW, NE, SE, SW
function sortMarkers() {
  var ns = markers.slice( 0 );
  var ew = markers.slice( 0 );

  ew.sort( function( a, b ) {
    if( a.position.lat() < b.position.lat() ) {
      return -1;
    }
    else if( a.position.lat() > b.position.lat() ) {
      return 1;
    }

    return 0;
  });

  ns.sort( function( a, b ) {
    if( a.position.lng() < b.position.lng() ) {
      return -1;
    }
    else if( a.position.lng() > b.position.lng() ) {
      return 1;
    }

    return 0;
  });

  var nw;
  var ne;
  var se;
  var sw;

  if( ew.indexOf( ns[0] ) > 1 ) {
    nw = ns[0];
  }
  else {
    ne = ns[0];
  }

  if( ew.indexOf( ns[1] ) > 1 ) {
    nw = ns[1];
  }
  else {
    ne = ns[1];
  }

  if( ew.indexOf( ns[2] ) > 1 ) {
    sw = ns[2];
  }
  else {
    se = ns[2];
  }

  if( ew.indexOf( ns[3] ) > 1 ) {
    sw = ns[3];
  }
  else {
    se = ns[3];
  }

  markers[0] = nw;
  markers[1] = ne;
  markers[2] = se;
  markers[3] = sw;
}

ありがとうございました。

32
Dave Jarvis

ポイントを考えると:

   4  +        [d]            [g]                 
      |                             
   3 [a]            [e]                 
      |                             
   2  +                  [f]       [h]    
      |                             
   1  +   [b]                             
      |                             
   0  +----+---[c]---+----+----+----+
      0    1    2    3    4    5    6

次のバウンドウォークを見つけたいと思います。

   4  +     ___[d]------------[g]                 
      |  __/                     \    
   3 [a]/           [e]__         \       
      | \             \_ ```---    \  
   2  +  \              `[f]   \___[h]    
      |   \           __/            
   1  +   [b]      __/                   
      |      \    /                
   0  +----+--`[c]---+----+----+----+
      0    1    2    3    4    5    6

これが正しければ、次の方法があります。

  • 最上点を見つける、P、ポイントのセットで。同点の場合は、x座標が最小の点を選択します
  • 勾配mを比較してすべての点を並べ替えます そしてMj 線の各ペアの点(Pを除く)!)P およびPj Pを通過するときに作る
    • mの場合 そしてMj 等しい場合、点Pを またはPj Pに最も近い 最初に来ます
    • mの場合 は正でmj 負(またはゼロ)、Pj 最初に来る
    • 両方の場合m そしてMj が正または負の場合、勾配が最大の線に属する点が最初に来るようにします

マップの簡単なデモは次のとおりです。

enter image description here

(私はJavaScriptをほとんど知らないので、JavaScriptコード規則に違反した可能性があります...):

var points = [
    new Point("Stuttgard", 48.7771056, 9.1807688),
    new Point("Rotterdam", 51.9226899, 4.4707867),
    new Point("Paris", 48.8566667, 2.3509871),
    new Point("Hamburg", 53.5538148, 9.9915752),
    new Point("Praha", 50.0878114, 14.4204598),
    new Point("Amsterdam", 52.3738007, 4.8909347),
    new Point("Bremen", 53.074981, 8.807081),
    new Point("Calais", 50.9580293, 1.8524129),
];
var upper = upperLeft(points);

print("points :: " + points);
print("upper  :: " + upper);
points.sort(pointSort);
print("sorted :: " + points);

// A representation of a 2D Point.
function Point(label, lat, lon) {

    this.label = label;
    this.x = (lon + 180) * 360;
    this.y = (lat + 90) * 180;

    this.distance=function(that) {
        var dX = that.x - this.x;
        var dY = that.y - this.y;
        return Math.sqrt((dX*dX) + (dY*dY));
    }

    this.slope=function(that) {
        var dX = that.x - this.x;
        var dY = that.y - this.y;
        return dY / dX;
    }

    this.toString=function() {
        return this.label;
    }
}

// A custom sort function that sorts p1 and p2 based on their slope
// that is formed from the upper most point from the array of points.
function pointSort(p1, p2) {
    // Exclude the 'upper' point from the sort (which should come first).
    if(p1 == upper) return -1;
    if(p2 == upper) return 1;

    // Find the slopes of 'p1' and 'p2' when a line is 
    // drawn from those points through the 'upper' point.
    var m1 = upper.slope(p1);
    var m2 = upper.slope(p2);

    // 'p1' and 'p2' are on the same line towards 'upper'.
    if(m1 == m2) {
        // The point closest to 'upper' will come first.
        return p1.distance(upper) < p2.distance(upper) ? -1 : 1;
    }

    // If 'p1' is to the right of 'upper' and 'p2' is the the left.
    if(m1 <= 0 && m2 > 0) return -1;

    // If 'p1' is to the left of 'upper' and 'p2' is the the right.
    if(m1 > 0 && m2 <= 0) return 1;

    // It seems that both slopes are either positive, or negative.
    return m1 > m2 ? -1 : 1;
}

// Find the upper most point. In case of a tie, get the left most point.
function upperLeft(points) {
    var top = points[0];
    for(var i = 1; i < points.length; i++) {
        var temp = points[i];
        if(temp.y > top.y || (temp.y == top.y && temp.x < top.x)) {
            top = temp;
        }
    }
    return top;
}

注:GISに関しては初心者なので、lat,lonからx,yへの変換を2回または3回チェックする必要があります!!!しかし、おそらくあなたは何も変換する必要さえありません。そうしないと、問題のポイントの場所によっては、upperLeft関数が最高点ではなく最低点を返す可能性があります。繰り返しますが、これらの仮定をトリプルチェックしてください!

上記のスニペットを実行すると、以下が出力されます。

points :: Stuttgard,Rotterdam,Paris,Hamburg,Praha,Amsterdam,Bremen,Calais
upper  :: Hamburg
sorted :: Hamburg,Praha,Stuttgard,Paris,Bremen,Calais,Rotterdam,Amsterdam

代替距離関数

function distance(lat1, lng1, lat2, lng2) {
  var R = 6371; // km
  var dLat = (lat2-lat1).toRad();
  var dLon = (lng2-lng1).toRad();
  var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
          Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
          Math.sin(dLon/2) * Math.sin(dLon/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  return R * c;
}
39
Bart Kiers

アルゴリズムのアイデア:ポリゴン内のポイントを取得するために4つのポイントを平均します。次に、説明されているように、逆三角関数を使用して、その中心点と各点の間の光線の角度を計算します ここ 。次に、角度で並べ替えます。これにより、並べ替え順序と「ゼロ度」と見なすものに応じて、(反)時計回りの順序が得られます。

更新:ここにいくつかのコードがあります。ほとんどテストされていませんが、それはアイデアです。

function sorted_points(points) {
    points = points.slice(0); // copy the array, since sort() modifies it
    var stringify_point = function(p) { return p.x + ',' + p.y; };

    // finds a point in the interior of `pts`
    var avg_points = function(pts) {
        var x = 0;
        y = 0;
        for(i = 0; i < pts.length; i++) {
            x += pts[i].x;
            y += pts[i].y;
        }
        return {x: x/pts.length, y:y/pts.length};
    }
    var center = avg_points(points);

    // calculate the angle between each point and the centerpoint, and sort by those angles
    var angles = {};
    for(i = 0; i < points.length; i++) {
        angles[stringify_point(points[i])] = Math.atan(points[i].x - center.x, points[i].y - center.y);
    }
    points.sort(function(p1, p2) {
        return angles[stringify_point(p1)] - angles[stringify_point(p2)];
    });
    return points;
}

ポイント({x: 1, y: 1}などのオブジェクトの配列)を反時計回りに並べ替えます。

4
kevingessner

1年後に同様の問題を抱えてここに到着した人のために:

私は選ばれた答えの限界の歩みに同意しません。与えられたクロック方向であっても、順序に対する特異な解決策はありません。与えられた座標の凸包は点eとfを排除します。これらは、パスに沿ってどこにでも取り付けることができます。客観的には、h、e、f、cをh、f、e、cに改善して、xコンポーネントの方向を一定に保つことができます(この場合は負)。

これの重要性により、選択したウォークで囲まれた結果のエリアにマップの場所が含まれることを保証できなくなります。

1
Adrian