Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Current »

Motivation

Many systems have data stored in GPS coordinates (latitude, longitude). To be able to integrate and showcase them in twinzo - Digital Twin platform, they need to be recalculated to local twinzo coordinates. This guide will show you how.

GPS items creation

In twinzo portal go to bookmark Places/GPS items (documentation here). Without knowing where North is on the layout, you need at least 3 GPS items with x, y, lat, lon Strong preference for the corners of the sector layout.

GPS items download

Here is a screenshot from Postman, showing how to get GPS items downloaded to your software. Three important parameters are Client, Branch and Api-Key. If you are missing any of them please ask your contact person to provide them.

GPS item data structure

In the coming steps, we will show you examples from our Kotlin language implementation of a data structure and calculation.

GpsItem(
        @SerializedName("id")
        var id: Int? = null,
        @SerializedName("lat")
        var latitude: Double? = null,
        @SerializedName("lon")
        var longitude: Double? = null,
        @SerializedName("timestamp")
        var timestamp: Long? = null,
        @SerializedName("x")
        var X: Float? = null,
        @SerializedName("y")
        var Y: Float? = null,
        @SerializedName("distance")
        var distance: Double? = null
)
class MyPoint extends Point{

  MyPoint({required double x, required double y}):  super (x, y);

  double angleTo(MyPoint in2){
    return Vector2(this.x as double, this.y as double).angleTo(Vector2(in2.x as double, in2.y as double));
  }

  MyPoint centerOfLineTo(MyPoint in2){
    return new MyPoint(
       x: ((this.x + in2.x) / 2) * 1.0,
       y: ((this.y + in2.y) / 2) * 1.0
    );
  }
}

GPS coordinates recalculation to twinzo coordinates

Now, with the established data structure we are ready to continue with the trilateration.

Distance from GPS item (myGpsItem contains my position in lat, lon only)

fun calculateDistanceForGpsItems(myGpsItem: GpsItem, GpsBeacons: ArrayList<GpsItem>) = runBlocking {
            var result: ArrayList<GpsItem> = ArrayList()
            for (item in GpsBeacons) {
                item.distance = distanceToGpsItems(myGpsItem.latitude!!, myGpsItem.longitude!!, item.latitude!!, item.longitude!!) * 1000
                result.add(item)
            }
            result
        } 

fun distanceToGpsItems(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
            val R = 6371000 // metres
            val fi1 = Math.toRadians(lat1)
            val fi2 = Math.toRadians(lat2)
            val deltafi = Math.toRadians(lat2 - lat1)
            val deltalambda = Math.toRadians(lon2 - lon1)

            val a = Math.sin(deltafi / 2) * Math.sin(deltafi / 2) + Math.cos(fi1) * Math.cos(fi2) * Math.sin(deltalambda / 2) * Math.sin(deltalambda / 2)
            val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
            val distance = R * c
            return distance
}

Positioning Item

A little bit of mathematics ahead of us.

class PositioningItem extends Equatable {
  MyPoint point;
  double? distance;

  List<MyPoint> getIntersectionsWith(PositioningItem inPositionigItem) {

    PositioningItem inPosItem = new PositioningItem(point: inPositionigItem.point, distance: inPositionigItem.distance);
    PositioningItem thisPosItem = new PositioningItem(point: this.point, distance: this.distance);

    List<MyPoint> result = [];

    double coefPlaceHolder = (thisPosItem.point.distanceTo(inPosItem.point) + thisPosItem.distance! +
        inPosItem.distance!) *
        (thisPosItem.point.distanceTo(inPosItem.point) + thisPosItem.distance! -
            inPosItem.distance!) *
        (thisPosItem.point.distanceTo(inPosItem.point) - thisPosItem.distance! +
            inPosItem.distance!) *
        (-thisPosItem.point.distanceTo(inPosItem.point) + thisPosItem.distance! +
            inPosItem.distance!);

    if(coefPlaceHolder < 0){
      //points doesnt have intersections
      result.add(thisPosItem._getClosestTangentTo(inPosItem));
      print("FLUTTER tang $thisPosItem ${inPosItem} ${result}");
      return result;
    }else {
      double coef = 0.25 * sqrt(coefPlaceHolder);


      result.add(MyPoint(
          x: (thisPosItem.point.x + inPosItem.point.x)
              / 2 + ((pow(thisPosItem.distance!, 2) - pow(inPosItem.distance!, 2))
              / (2 * pow(thisPosItem.point.distanceTo(inPosItem.point), 2))
              * (inPosItem.point.x - thisPosItem.point.x)
              + (2 * coef * (inPosItem.point.y - thisPosItem.point.y))
                  / pow(thisPosItem.point.distanceTo(inPosItem.point), 2)),
          y: (thisPosItem.point.y + inPosItem.point.y)
              / 2 + ((pow(thisPosItem.distance!, 2) - pow(inPosItem.distance!, 2))
              / (2 * pow(thisPosItem.point.distanceTo(inPosItem.point), 2))
              * (inPosItem.point.y - thisPosItem.point.y)
              - (2 * coef * (inPosItem.point.x - thisPosItem.point.x))
                  / pow(thisPosItem.point.distanceTo(inPosItem.point), 2))
      )
      );
      result.add(MyPoint(
          x: (thisPosItem.point.x + inPosItem.point.x)
              / 2 + ((pow(thisPosItem.distance!, 2) - pow(inPosItem.distance!, 2))
              / (2 * pow(thisPosItem.point.distanceTo(inPosItem.point), 2))
              * (inPosItem.point.x - thisPosItem.point.x)
              - (2 * coef * (inPosItem.point.y - thisPosItem.point.y))
                  / pow(thisPosItem.point.distanceTo(inPosItem.point), 2)),
          y: (thisPosItem.point.y + inPosItem.point.y)
              / 2 + ((pow(thisPosItem.distance!, 2) - pow(inPosItem.distance!, 2))
              / (2 * pow(thisPosItem.point.distanceTo(inPosItem.point), 2))
              * (inPosItem.point.y - thisPosItem.point.y)
              + (2 * coef * (inPosItem.point.x - thisPosItem.point.x))
                  / pow(thisPosItem.point.distanceTo(inPosItem.point), 2))
      )
      );

      return result;
    }
  }

Needed functions (getClosestTangentTo & __calculateWeightedAverageFromIntersects)

  MyPoint _getClosestTangentTo( PositioningItem inPosItem){

    PositioningItem inPositioningItem = new PositioningItem(point: inPosItem.point, distance: inPosItem.distance);
    PositioningItem thisPositioningItem = this;

      double distanceBetweenCircles = (thisPositioningItem.point.distanceTo(inPositioningItem.point) - thisPositioningItem.distance! - inPositioningItem.distance!);
      double oneSquare = distanceBetweenCircles/(thisPositioningItem.distance! + inPositioningItem.distance!);

      thisPositioningItem.distance = thisPositioningItem.distance! + thisPositioningItem.distance! * oneSquare;
      inPositioningItem.distance = inPositioningItem.distance! + inPositioningItem.distance! * oneSquare;

      if((thisPositioningItem.point.distanceTo(inPositioningItem.point) - thisPositioningItem.distance! - inPositioningItem.distance!) > 0){
        thisPositioningItem.distance = thisPositioningItem.distance! + 0.0010;
        inPositioningItem.distance = inPositioningItem.distance! + 0.0010;
      }

      List<MyPoint> mToBeAveraged = thisPositioningItem.getIntersectionsWith(inPositioningItem);

      double x = 0.0;
      mToBeAveraged.forEach((element) {x += element.x;});
      x = x/mToBeAveraged.length;

      double y = 0.0;
      mToBeAveraged.forEach((element) {y += element.y;});
      y = y/mToBeAveraged.length;

      return MyPoint(x: x, y:y);
  }

}

MyPoint _calculateWeightedAverageFromIntersects(List<PositioningItem> inPoints, List<MyPoint> intersect12, List<MyPoint> intersect13, List<MyPoint> intersect23){
  double SumOfDistances = 0;
  inPoints.forEach((element) {
    SumOfDistances += element.distance!;
  });

  double w12 = (SumOfDistances - (inPoints[0].distance! + inPoints[1].distance!))/SumOfDistances;
  double w13 = (SumOfDistances - (inPoints[0].distance! + inPoints[2].distance!))/SumOfDistances;
  double w23 = (SumOfDistances - (inPoints[1].distance! + inPoints[2].distance!))/SumOfDistances;
  print("WeightedAverage weights $w12 , $w13 , $w23 ; SumOfWeights (should be 1) ${w12+w13+w23} ");


  /**SMALLEST TRIANGLE FROM INTERSECTS**/
  MyPoint? A;
  late MyPoint B;
  late MyPoint C;

  intersect12.forEach((a) {
    intersect13.forEach((b) {
      intersect23.forEach((c) {
        if(A == null){
          A = a;
          B = b;
          C = c;
        }else{
          if((a.distanceTo(b) + a.distanceTo(c) + b.distanceTo(c)) < (A!.distanceTo(B) + A!.distanceTo(C) + B.distanceTo(C))){
            A = a;
            B = b;
            C = c;
          }
        }

      });
    });
  });

  return MyPoint(
      x: (A!.x * w12) + (B.x * w13) + (C.x * w23),
      y: (A!.y * w12) + (B.y * w13) + (C.y * w23)
  );

}

CalculatePosition

And finally, the calculatePosition function will give us the desired result.

MyPoint? calculatePosition( List<PositioningItem> inPoints ) {
          List<MyPoint> intersect01 = inPoints[0].getIntersectionsWith(inPoints[1]);
          List<MyPoint> intersect02 = inPoints[0].getIntersectionsWith(inPoints[2]);
          List<MyPoint> intersect12 = inPoints[1].getIntersectionsWith(inPoints[2]);

          return _calculateWeightedAverageFromIntersects(inPoints, intersect01, intersect02, intersect12);
      }
   
} 

This position, in combination with Sector from where you obtained GPS items is enough to provide data to twinzo - Digital Twin platform.

  • No labels