Javaで最も単純な三角測量

良い一日!

私のプロジェクトの1つで適用した、1つの興味深い問題とその解決策についてお話したいと思います。

問題の本質はこれです:

いくつかの信号検出器(GSM基地局など)があります。 また、これらの検出器は、特定のソースのサーバーに信号レベルを送信します。 地図上でソースの座標を計算して表示する必要があります

これを行う方法に興味がある場合は、猫にようこそ。



パート1.理論と少しの幾何学


A(xa、ya)、B(xb、yb)、C(xc、yc)を、特定の直交座標系で指定された座標を持つ信号検出器とします。 O(xo、yo)は信号ソースです。

学校の物理学の教科書から、信号の振幅はソースまでの距離の2乗に反比例することを思い出してください。 したがって、線源Oから検出器Aまでの距離Raは、



ここで、kaはデバイスのキャリブレーション時に取得できる係数です。

ジオメトリの学校のコースからの距離a1を計算するための式を使用します(ここで指定するのは残念ですが、完全を期すためにしましょう)。



媒体の不均一性は無視されます。 はい、これは何らかのエラーを与えますが、この問題の解決策は問題の範囲を超えています。 信号の伝播に影響を与える定数因子(たとえば、壁の存在と材料)は、係数kaに既に含まれていると仮定します。 一時的な要因-干渉、信号の再反射など -それらは結果に影響しますが、実際には、屋内でもあまり干渉しません。

したがって、すべてのソースからの距離の依存関係式があります。

問題を「正面から」解決する場合、2次方程式のシステムがあり、その解は座標xo、yoを持つ点を与えます。 問題は、線形代数の大学のコースを少し忘れたことです。 一週間考えた後、私はこのように出会いました。

半径が信号レベルに反比例する各検出器の周りに円を描いてみましょう。 すべての円の交差領域に信号源が含まれます。 簡単にするために、ソースがこの領域の中心にあると想定できます。

3つの検出器と1つのソースを持つケースの図面を作成しています。



ここで、BとAは信号源に最も近いため、円の直径は小さくなります。 Cが最も遠く、直径が大きい。 円の直径の信号レベルへの依存性は実験的に決定されます。 この図面は実際の座標(たとえば、メートル)で構築されています。練習するために、最も一般的な方法でそれらをピクセルに変換します。 マップの寸法(幅、高さ)をピクセルとメートルで知る必要があります。 投影の非直線性(衛星画像の場合)は、以前と同様に無視されます。 私のプロジェクトでは大規模なスキームを使用する方が良いでしょう。これはフロアプランでした。

パート2.練習に移りましょう


それで、簡単な解決策が見つかったので、それを実践してみましょう。 私のプロジェクトのサーバー部分はJavaで開発され、json形式のSSL暗号化を使用して、TCPを介して検出器からデータを受け取ります。 コードのこの部分は独立しており、別のスレッドで実行されます。 受信したイベントはmysqlデータベースに保存されます。

三角測量は独自のスレッドで機能し、まだ処理されていないイベントをmysqlデータベースから選択します(現在時刻の3秒前まで-すべてのソースから同じ信号に関する情報を取得するため)。

解決策は単純で、数学を必要としませんが、円の交点を計算する必要があります。 幸いなことに、Javaにはすべての処理を行うAWTライブラリがあります。 ウィンドウインターフェースがなくても気にならず、クラスの一部はバックエンドのサーバーで正常に動作します。

私はプロジェクトからわずかに簡略化されたコードをもたらします。

public static TriangulationPoint triangulate(AlertDbo sourceAlert, AlertDbo[] children, MapFileDbo map) { … double ppx = map.getWidth() / map.getRealWidth(); //pixels per feet horizontal double ppy = map.getHeight() / map.getRealHeight(); //pixels per feet vertical
      
      





そのため、ご理解のとおりppx / ppy-これらは実座標をピクセルに変換するための係数です。

最初の検出器の信号レベルを取得し、光源までの距離を計算し、取得した半径でわかっている座標に従って円を作成します。

  double strength = sourceAlert.getSignalStrength(); double distance = calculateDistance(strength *sourceAlert.getDetector().getKoef()); MapCoordDbo coord = sourceAlert.getCoords(); … Area area = new Area(new Ellipse2D.Double(coord.getX(), coord.getY(), distance*2, distance*2));
      
      





ここで、残りのすべてのソースを調べて、同じことを行います。 最初の円と結果の円を交差させ、残りの検出器のループで結果を使用します。 追加のトリック-部屋の長方形と交差します。そうしないと、結果がそれを超えることがありますが、必要ありません。

  for(int j=0;j<children.length;j++) { … strength = children[j].getSignalStrength(); distance = calculateDistance(strength * children[j].getDetector().getKoef()); coord = children[j].getCoords(); area.intersect(new Area( new Ellipse2D.Double(coord.getX(), coord.getY(), distance*2, distance*2) )); area.intersect(new Area(new Rectangle(0, 0, map.getWidth(), map.getHeight()))); }
      
      







最後に、結果として少なくとも何かが残っていることを確認し、さらに計算誤差を計算します(結果の領域が内接する長方形の対角線の半分として)。



  if(area.getBounds().getWidth()>0 && area.getBounds().getHeight()>0) { TriangulationPoint tp = new TriangulationPoint(); tp.x = area.getBounds().getCenterX(); tp.y = area.getBounds().getCenterY(); double dx = area.getBounds().getWidth() / 2; double dy = area.getBounds().getHeight() / 2; tp.err = Math.sqrt(dx*dx + dy*dy) /2; return tp; } return null; }
      
      





これは距離計算関数です。

 public static double calculateDistance(double db) { double[] dbs = {…}; double[] fts = {…}; double koef = -…; double prevDb = 70; double prevDist = 0; for(int i=0;i<dbs.length;i++) { if(dbs[i]<db) { break; } koef = (dbs[i] - prevDb) / (fts[i] - prevDist); prevDb = dbs[i]; prevDist = fts[i]; } return prevDist + koef * (db - prevDb); }
      
      





以前は対数関数でしたが、残念ながら、実際の信号の減衰は異なります。実験室のいくつかのポイントで信号レベルを調べ、信号レベルと対応する距離を2つの配列に書き留めなければなりませんでした。

念のため、NDAに違反しないように実数を削除しました。

ところで、C#でもまったく同じ解決策が可能です。 Areaの代わりに、GraphicsPathを使用する必要があります。Intersectメソッドがあります。 唯一の困難は、結果の交差点の中心を見つけることです。 C#で領域の中心を見つけるためのコードは次のとおりです。

  private PointF RegionCentroid(Region region, Matrix transform) { float mx = 0; float my = 0; float total_weight = 0; foreach (RectangleF rect in region.GetRegionScans(transform)) { float rect_weight = rect.Width * rect.Height; mx += rect_weight * (rect.Left + rect.Width / 2f); my += rect_weight * (rect.Top + rect.Height / 2f); total_weight += rect_weight; } return new PointF(mx / total_weight, my / total_weight); }
      
      





そのため、数学と物理学の十分な知識がなくても、特定の仮定を立てると、かなり複雑な問題を解決することができます。

ご清聴ありがとうございました!



All Articles