35

問題

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

質問

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

ソリューションと検索

StackOverflow に関する質問

関連サイト

既知のアルゴリズム

  • グラハムのスキャン (複雑すぎる)
  • Jarvis March アルゴリズム (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;
}

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

4

3 に答える 3

43

ポイントを考えると:

   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 topを見つけます。同点の場合は、x 座標が最も小さい点を選択します。
  • 点の各ペア (P top を除く)の傾き m iと m jを比較して、すべての点を並べ替える (P topを除く) P iと P jが P topを通過するときに作成する
    • m iと m jが等しい場合、 P topに最も近い点 P iまたは P jが最初に来ます。
    • m iが正で m jが負 (またはゼロ) の場合、P jが最初に来る
    • m iと m jの両方が正または負の場合、傾きが最大の直線に属する点が最初になるようにします。

マップの簡単なデモを次に示します。

ここに画像の説明を入力

(私は 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への変換を 2 回または 3 回確認する必要があります!!! x,yしかし、おそらく何も変換する必要さえありません。そうしないと、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;
}
于 2010-05-19T06:41:48.763 に答える
6

アルゴリズムのアイデア: 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}) を反時計回りに並べ替えます。

于 2010-05-18T17:51:14.857 に答える
1

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

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

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

于 2011-08-05T14:22:46.837 に答える