diff --git a/src/edu/nps/moves/dis7/utilities/stream/X3dCoordinates.java b/src/edu/nps/moves/dis7/utilities/stream/X3dCoordinates.java new file mode 100644 index 0000000000000000000000000000000000000000..17d48c4595c90efa053809311ec0522a4347aa00 --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/stream/X3dCoordinates.java @@ -0,0 +1,85 @@ +package edu.nps.moves.dis7.utilities.stream; + +/** + * This class is a holder for coordinates and angles of ESPDUs to store them in + * HashMaps + * + * @author Tobias Brennenstuhl @ NPS + */ +public class X3dCoordinates { + + private double x; + private double y; + private double z; + private double phi; + private double psi; + private double theta; + + public X3dCoordinates(double x, double y, double z, double phi, double psi, double theta) { + this.setX(x); + this.setY(y); + this.setZ(z); + this.setPhi(phi); + this.setPsi(psi); + this.setTheta(theta); + + } + + public X3dCoordinates() { + this.setX(0.0); + this.setY(0.0); + this.setZ(0.0); + this.setPhi(0.0); + this.setPsi(0.0); + this.setTheta(0.0); + } + + public double getPhi() { + return phi; + } + + public void setPhi(double phi) { + this.phi = phi; + } + + public double getPsi() { + return psi; + } + + public void setPsi(double psi) { + this.psi = psi; + } + + public double getTheta() { + return theta; + } + + public void setTheta(double theta) { + this.theta = theta; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + +} diff --git a/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java b/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java new file mode 100644 index 0000000000000000000000000000000000000000..4adb89e15b517948f6292a380429611bacacf6e1 --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/stream/X3dCreateInterpolators.java @@ -0,0 +1,287 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package edu.nps.moves.dis7.utilities.stream; + +import edu.nps.moves.dis7.EntityStatePdu; +import edu.nps.moves.dis7.Pdu; +import edu.nps.moves.dis7.enumerations.DISPDUType; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.nio.ByteBuffer; +import java.text.NumberFormat; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Tobias Brennenstuhl @ NPS + */ +public class X3dCreateInterpolators { + + private byte[] bufferShort; + + // -------------------- Begin Variables for Position Interpolator + private Boolean firstTimeStamp = true; + private int firstLocalTimeStamp = 0; + + private double firstLocalX = 0; + private double firstLocalY = 0; + private double firstLocalZ = 0; + private double firstLocalPhi = 0; + private double firstLocalPsi = 0; + private double firstLocalTheta = 0; + + private LinkedHashMap<Double, X3dCoordinates> testMap = new LinkedHashMap<>(); + + //Setting up a NumberFormatter for limitting the decimal count to 3 + private NumberFormat coordinateNumberFormat = NumberFormat.getInstance(new Locale("en", "US")); + + // -------------------- End Variables for Position Interpolator + public X3dCreateInterpolators() { + + //3 significant digits equals milimeter position accuracy and 0.001 radians = 0.0572963266634555‬ degrees + coordinateNumberFormat.setMaximumFractionDigits(3); + + } + + public void addPointsToMap(byte[] localBufferShort) { + + this.bufferShort = localBufferShort.clone(); + + if (bufferShort[2] == 1) { + + //PDU Factory + PduFactory pduFactory = new PduFactory(); + Pdu localPdu = null; + + localPdu = pduFactory.createPdu(bufferShort); + + // ToDO figure out how to do this! makeEntityStatePDU + EntityStatePdu localEspdu = pduFactory.makeEntityStatePdu(); + //Put all the data we need into the localEspdu + ByteBuffer espduBuffer = ByteBuffer.wrap(bufferShort); + try { + localEspdu.unmarshal(espduBuffer); + } catch (Exception ex) { + Logger.getLogger(X3dCreateInterpolators.class.getName()).log(Level.SEVERE, null, ex); + } + + double localTimeStamp = 0; + double localX = 0; + double localY = 0; + double localZ = 0; + + double localPhi = 0; + double localPsi = 0; + double localTheta = 0; + + //Store the first timestamp to subtract it from all others + //Same with X,Y,Z to create a local coordiante system + if (firstTimeStamp) { + + firstLocalTimeStamp = localPdu.getTimestamp(); + localTimeStamp = localPdu.getTimestamp(); + firstLocalX = localEspdu.getEntityLocation().getX(); + firstLocalY = localEspdu.getEntityLocation().getZ(); + firstLocalZ = -1 * localEspdu.getEntityLocation().getY(); + + firstTimeStamp = false; + } + + localTimeStamp = localPdu.getTimestamp(); + localX = localEspdu.getEntityLocation().getX(); + localY = localEspdu.getEntityLocation().getZ(); + localZ = -1 * localEspdu.getEntityLocation().getY(); + localPhi = localEspdu.getEntityOrientation().getPhi(); + localPsi = localEspdu.getEntityOrientation().getPsi(); + localTheta = localEspdu.getEntityOrientation().getTheta(); + + localTimeStamp = localTimeStamp - firstLocalTimeStamp; + localX = localX - firstLocalX; + localY = localY - firstLocalY; + localZ = localZ - firstLocalZ; + + //Divide TimeStamp by 1,300,000 to get something close to a second per Unit. + //According to the DIS standard one tick is 3600/(2^31) seconds ~ 1.6764 µs + //1,100,000 was derived from a stream that is 83 seconds long. The number was adjusted to get a timesensor with 83 seconds + //ToDo find the real conversion between TimeStampDelta and seconds + localTimeStamp = localTimeStamp / 1100000; + + //Only add to stream if it is an ESPDU + //ToDo: Add support for multiple Entities + if ((localPdu.getPduType() != null) && (localPdu.getPduType() == DISPDUType.ENTITY_STATE)) { + + testMap.put((double) localTimeStamp, new X3dCoordinates(localX, localY, localZ, localPhi, localPsi, localTheta)); + + } + } + + } + + public void makeX3dInterpolator() { + + //Compression of the testMap. + //Remove all collinear points. + X3dSlidingWindowCompression slidingWindowCompression = new X3dSlidingWindowCompression(testMap); + + TreeMap<Double, X3dCoordinates> returnMap = new TreeMap<>(); + + //To turn of the compression just comment the next line out and the very next in. + returnMap = slidingWindowCompression.doSlidingWindow(); + //returnMap.putAll(testMap); + + //Writing all values from the KeyMap to a proper Position Interpolator String + System.out.println("Writing Position and Rotation Interpolator"); + Set<Double> keys = returnMap.keySet(); + //Set<Double> keys = tempKeyKeyValueSetPositionInterPolator.keySet(); + String positionKey = "key = '"; + String positionKeyValue = "keyValue = '"; + String positionInterpolatorToCopy = "<PositionInterpolator DEF='EntityPosition' "; + + String orientationKeyX = "key = '"; + String orientationKeyValueX = "keyValue = '"; + String orientationInterpolatorToCopyX = "<OrientationInterpolator DEF='EntityOrientationX' "; + + String orientationKeyY = "key = '"; + String orientationKeyValueY = "keyValue = '"; + String orientationInterpolatorToCopyY = "<OrientationInterpolator DEF='EntityOrientationY' "; + + String orientationKeyZ = "key = '"; + String orientationKeyValueZ = "keyValue = '"; + String orientationInterpolatorToCopyZ = "<OrientationInterpolator DEF='EntityOrientationZ' "; + + //Find highest time to do the normalization + double lastTimeStamp = 0; + + for (Double k : keys) { + + if (k > lastTimeStamp) { + + lastTimeStamp = k; + + } + } + + //Normalize all times in the set + LinkedHashMap keyKeyValueSetPositionInterpolator = new LinkedHashMap<Double, String>(); + + LinkedHashMap keyKeyValueSetOrientationInterpolatorX = new LinkedHashMap<Double, String>(); + LinkedHashMap keyKeyValueSetOrientationInterpolatorY = new LinkedHashMap<Double, String>(); + LinkedHashMap keyKeyValueSetOrientationInterpolatorZ = new LinkedHashMap<Double, String>(); + + for (Double k : keys) { + + String localCoordinateString; + String localOrientationStringX; + String localOrientationStringY; + String localOrientationStringZ; + + double tempX = returnMap.get(k).getX(); + double tempY = returnMap.get(k).getY(); + double tempZ = returnMap.get(k).getZ(); + + double tempPhi = returnMap.get(k).getPhi() / 6.28; + double tempPsi = returnMap.get(k).getPsi() / 6.28; + double tempTheta = returnMap.get(k).getTheta() / 6.28; + + localCoordinateString = " " + coordinateNumberFormat.format(tempX) + " " + coordinateNumberFormat.format(tempY) + " " + coordinateNumberFormat.format(tempZ); + localOrientationStringX = " 1 0 0 " + coordinateNumberFormat.format(tempPhi); + localOrientationStringY = " 0 1 0 " + coordinateNumberFormat.format(tempTheta); + localOrientationStringZ = " 0 0 1 " + coordinateNumberFormat.format(tempPsi); + + keyKeyValueSetPositionInterpolator.put(k / lastTimeStamp, localCoordinateString); + keyKeyValueSetOrientationInterpolatorX.put(k / lastTimeStamp, localOrientationStringX); + keyKeyValueSetOrientationInterpolatorY.put(k / lastTimeStamp, localOrientationStringY); + keyKeyValueSetOrientationInterpolatorZ.put(k / lastTimeStamp, localOrientationStringZ); + + } + + keys = keyKeyValueSetPositionInterpolator.keySet(); + + //Setting up the timeSensor + //Only one timeSensor for both interpolators is needed + String timeSensor = "<TimeSensor DEF='PduStreamClock' cycleInterval='"; + + timeSensor += lastTimeStamp; + + timeSensor += "' loop = 'true'/>"; + + //Printing the timeSensor to the console + System.out.println(timeSensor); + + //Setting up PositionInterpolator and OrientationInterpolator + for (Double k : keys) { + //System.out.println("Time: " + k + " Position (x,y,z) " + keyKeyValueSetPositionInterpolator.get(k)); + + //PositionInterpolator + positionKey += coordinateNumberFormat.format(k) + " "; + positionKeyValue += keyKeyValueSetPositionInterpolator.get(k) + " "; + + //OrientationInterpolator for X (phi) + orientationKeyX += coordinateNumberFormat.format(k) + " "; + orientationKeyValueX += keyKeyValueSetOrientationInterpolatorX.get(k) + " "; + + //OrientationInterpolator for Y (theta) + orientationKeyY += coordinateNumberFormat.format(k) + " "; + orientationKeyValueY += keyKeyValueSetOrientationInterpolatorY.get(k) + " "; + + //OrientationInterpolator for Z (psi) + orientationKeyZ += coordinateNumberFormat.format(k) + " "; + orientationKeyValueZ += keyKeyValueSetOrientationInterpolatorZ.get(k) + " "; + + } + positionKey += "' "; + positionKeyValue += "' "; + + orientationKeyX += "' "; + orientationKeyValueX += "' "; + + orientationKeyY += "' "; + orientationKeyValueY += "' "; + + orientationKeyZ += "' "; + orientationKeyValueZ += "' "; + + //PositionInterpolator + positionInterpolatorToCopy += positionKey + "\n"; + positionInterpolatorToCopy += positionKeyValue; + positionInterpolatorToCopy += "/>"; + + //PositionInterpolator for X + orientationInterpolatorToCopyX += orientationKeyX + "\n"; + orientationInterpolatorToCopyX += orientationKeyValueX; + orientationInterpolatorToCopyX += "/>"; + + //PositionInterpolator for Y + orientationInterpolatorToCopyY += orientationKeyY + "\n"; + orientationInterpolatorToCopyY += orientationKeyValueY; + orientationInterpolatorToCopyY += "/>"; + + //PositionInterpolator for Z + orientationInterpolatorToCopyZ += orientationKeyY + "\n"; + orientationInterpolatorToCopyZ += orientationKeyValueZ; + orientationInterpolatorToCopyZ += "/>"; + + //Printing PositionInterpolator to the console + System.out.println(positionInterpolatorToCopy); + + //First Rotation must be around z axis by psi + //Printing OrientationInterpolator for X to the console + System.out.println(orientationInterpolatorToCopyZ); + + //Second Rotation must be around resulting y (y') axis by theta + //Printing OrientationInterpolator for Y to the console + System.out.println(orientationInterpolatorToCopyY); + + //last rotation must be around resulting x (x') axis by phi + //Printing OrientationInterpolator for Z to the console + System.out.println(orientationInterpolatorToCopyX); + } + +} diff --git a/src/edu/nps/moves/dis7/utilities/stream/X3dSlidingWindowCompression.java b/src/edu/nps/moves/dis7/utilities/stream/X3dSlidingWindowCompression.java new file mode 100644 index 0000000000000000000000000000000000000000..21f311f1e00fb4e94b22fc7375f081fe4e43e6e0 --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/stream/X3dSlidingWindowCompression.java @@ -0,0 +1,135 @@ +package edu.nps.moves.dis7.utilities.stream; + +import static java.lang.Math.pow; +import static java.lang.Math.sqrt; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; +import java.util.TreeMap; + +/** + * + * @author Tobias Brennenstuhl @ NPS + */ +public class X3dSlidingWindowCompression { + + private LinkedHashMap<Double, X3dCoordinates> localMap; + + public X3dSlidingWindowCompression(LinkedHashMap<Double, X3dCoordinates> localHashMap) { + + this.localMap = new LinkedHashMap<>(); + Set<Double> keys = localHashMap.keySet(); + for (Double k : keys) { + localMap.put(k, localHashMap.get(k)); + } + } + + public TreeMap<Double, X3dCoordinates> doSlidingWindow() { + + System.out.println("DISTools.Regression.doRegression()"); + //Check whether points could be deleted to compress the stream + //https://www.crashkurs-statistik.de/einfache-lineare-regression/ + TreeMap<Double, X3dCoordinates> streamMap = new TreeMap<>(); + //Copy LinkedHashMap into TreeMap to be able to pull the first element. + streamMap.putAll(localMap); + TreeMap<Double, X3dCoordinates> returnMap = new TreeMap<>(); + //TreeMap of slidingWindows will store all of the points that are currently processed + //use .pullFirstEntry() to get rid of the points at the beginning. + TreeMap<Double, X3dCoordinates> slidingWindow = new TreeMap<>(); + + while (streamMap.size() > 0) { + slidingWindow.put(streamMap.firstEntry().getKey(), streamMap.get(streamMap.firstEntry().getKey())); + streamMap.pollFirstEntry(); + + //Calculate the mean and SD + Set<Double> slidingWindowKeys = slidingWindow.keySet(); + + if (slidingWindow.size() >= 3) { + + List<Double> tList = new ArrayList<>(); + List<Double> xList = new ArrayList<>(); + List<Double> yList = new ArrayList<>(); + List<Double> zList = new ArrayList<>(); + List<Double> phiList = new ArrayList<>(); + List<Double> psiList = new ArrayList<>(); + List<Double> thetaList = new ArrayList<>(); + + Double[] k = new Double[slidingWindowKeys.size()]; + slidingWindowKeys.toArray(k); + + for (int i = 0; i < slidingWindow.size(); i++) { + + tList.add(i, k[i]); + + phiList.add(i, slidingWindow.get(k[i]).getPhi()); + psiList.add(i, slidingWindow.get(k[i]).getPsi()); + thetaList.add(i, slidingWindow.get(k[i]).getTheta()); + xList.add(i, slidingWindow.get(k[i]).getX()); + yList.add(i, slidingWindow.get(k[i]).getY()); + zList.add(i, slidingWindow.get(k[i]).getZ()); + + } + + //Calculate Area of Triangle + //Credit: http://www.ambrsoft.com/TrigoCalc/Line3D/LineColinear.htm + for (int i = 0; i < slidingWindow.size(); i++) { + + double a = sqrt(pow(xList.get(1) - xList.get(0), 2) + pow(yList.get(1) - yList.get(0), 2) + pow(zList.get(1) - zList.get(0), 2)); + double b = sqrt(pow(xList.get(i) - xList.get(0), 2) + pow(yList.get(i) - yList.get(0), 2) + pow(zList.get(i) - zList.get(0), 2)); + double c = sqrt(pow(xList.get(i) - xList.get(1), 2) + pow(yList.get(i) - yList.get(1), 2) + pow(zList.get(i) - zList.get(1), 2)); + double s = (a + b + c) / 2; + double areaA = sqrt(s * (s - a) * (s - b) * (s - c)); + + if ((areaA >= 0.1) || (tList.get(i) - tList.get(0) >= 4.0)) { + //grab the first and the last point from the sliding window and push it to the returnMap + X3dCoordinates firstPoint = new X3dCoordinates(); + firstPoint.setX(xList.get(0)); + firstPoint.setY(yList.get(0)); + firstPoint.setZ(zList.get(0)); + firstPoint.setPhi(phiList.get(0)); + firstPoint.setPsi(psiList.get(0)); + firstPoint.setTheta(thetaList.get(0)); + + X3dCoordinates lastPoint = new X3dCoordinates(xList.get(i), yList.get(i), zList.get(i), phiList.get(i), psiList.get(i), thetaList.get(i)); + + returnMap.put(tList.get(0), firstPoint); + returnMap.put(tList.get(i), lastPoint); + + slidingWindow.clear(); + + tList.clear(); + xList.clear(); + yList.clear(); + zList.clear(); + phiList.clear(); + psiList.clear(); + thetaList.clear(); + + break; + } + + if ((areaA <= 0.1) && (tList.get(i) - tList.get(0) <= 4.0) && streamMap.size() == 0) { + + //System.out.println("StreamMap empty. All points left will be added. Break"); + //grab the first and the last point from the siding window and push it to the returnMap + for (int j = 0; j < slidingWindow.size(); j++) { + X3dCoordinates leftPoints = new X3dCoordinates(xList.get(j), yList.get(j), zList.get(j), phiList.get(j), psiList.get(j), thetaList.get(j)); + returnMap.put(tList.get(j), leftPoints); + } + + break; + } + //System.out.println("Area of Triangle: " + areaA); + } + + } + + } + + return returnMap; + + } +; + +}