2022-01-07 10:41:38 +00:00
package Array ;
import java.awt.Frame ;
import java.io.FileInputStream ;
import java.io.FileOutputStream ;
import java.io.ObjectInputStream ;
import java.io.ObjectOutputStream ;
import java.io.Serializable ;
import java.util.ArrayList ;
import java.util.Arrays ;
2024-08-27 11:48:16 +00:00
2022-01-07 10:41:38 +00:00
import javax.swing.JFrame ;
import Array.importHydrophoneData.HydrophoneImport ;
import Array.importHydrophoneData.StreamerImport ;
import Array.layoutFX.ArrayGUIFX ;
import Array.plot.ArrayPlotProviderFX ;
import Array.sensors.swing.ArraySensorPanelProvider ;
import GPS.GPSControl ;
import GPS.GPSDataBlock ;
import GPS.GpsData ;
2024-03-12 13:44:24 +00:00
import GPS.GpsDataUnit ;
2022-01-07 10:41:38 +00:00
import PamController.PamControlledUnit ;
import PamController.PamControlledUnitGUI ;
import PamController.PamControlledUnitSettings ;
import PamController.PamController ;
2024-08-27 11:48:16 +00:00
import PamController.PamControllerInterface ;
2022-01-07 10:41:38 +00:00
import PamController.PamGUIManager ;
import PamController.PamSettingManager ;
import PamController.PamSettings ;
2024-03-12 13:44:24 +00:00
import PamController.masterReference.MasterReferencePoint ;
2022-01-07 10:41:38 +00:00
import PamController.positionreference.PositionReference ;
import PamModel.PamModuleInfo ;
import PamUtils.PamUtils ;
import PamView.PamControlledGUISwing ;
import PamView.WrapperControlledGUISwing ;
import PamView.importData.ImportDataSystem ;
import PamguardMVC.PamDataUnit ;
import PamguardMVC.PamObservable ;
import PamguardMVC.PamObserver ;
import dataPlotsFX.data.TDDataProviderRegisterFX ;
2024-08-27 11:48:16 +00:00
import pamMaths.PamQuaternion ;
import pamMaths.PamVector ;
import userDisplay.UserDisplayControl ;
2022-01-07 10:41:38 +00:00
/ * *
* Manager for different array configurations . Each array configuration is
* stored in it ' s own serialised file , which is separate from other Pam Settings
* so that they can be stored , emailed around , etc . In it ' main serialised file
* controlled by PamSettingsManager , Pamguard keeps a list of recently used arrays .
* < p >
* The dialog ArrayDialog allows the user to load arrays from file , create new arrays ,
* edit arrays , etc .
*
* @author Doug Gillespie
* @see Array . PamArray
*
* /
public class ArrayManager extends PamControlledUnit implements PamSettings , PamObserver , PositionReference {
/ * *
* No array specified
* /
public static final int ARRAY_TYPE_NONE = 0 ;
/ * *
* Point array ( single phone or multiple phones at same point )
* /
public static final int ARRAY_TYPE_POINT = 1 ;
/ * *
* Line array of two or more elements
* /
public static final int ARRAY_TYPE_LINE = 2 ;
/ * *
* Three or more Hydrophones all in the same plane
* /
public static final int ARRAY_TYPE_PLANE = 3 ;
/ * *
* Four or more hydrophones not in the same plane .
* /
public static final int ARRAY_TYPE_VOLUME = 4 ;
private static ArrayManager singleInstance = null ;
private HydrophoneProcess hydrophonesProcess ;
private static final String arrayFileType = " paf " ;
ArrayList < PamArray > recentArrays ;
// private DepthControl depthControl;
2023-10-09 13:56:12 +00:00
private ImportDataSystem < Hydrophone > hydrophoneImportManager ;
2022-01-07 10:41:38 +00:00
private ImportDataSystem < ArrayList < Double > > streamerImportManager ;
/ * *
* The FGX GUI for the array manager
* /
private ArrayGUIFX arrayGUIFX ;
/ * *
* The array manager .
* /
public static String arrayManagerType = " Array Manager " ;
/ * *
* The Swing GUI for the array manager , .
* /
private PamControlledGUISwing arrayGUISwing ;
public static final double DEFAULT_HYDROPHONE_SENSITIVITY = - 170 ;
public static final double DEFAULT_PREAMP_GAIN = 0 ;
private ArrayManager ( String unitName ) {
super ( arrayManagerType , " Array Manager " ) ;
/ *
* set this immediately to stop it calling back into itself when the process is added
* causing the getArrayManager ( ) to be called over and over .
* /
singleInstance = this ;
/ *
* Move these lines here from the getArrayManager ( ) static function which was a daft place to have them
* and also make sure they are created before the arrays are loaded from memory .
* /
hydrophonesProcess = new HydrophoneProcess ( this ) ;
addPamProcess ( hydrophonesProcess ) ;
PamSettingManager . getInstance ( ) . registerSettings ( this ) ;
// // if it hasn't created an array by now, do so !
if ( recentArrays = = null | | recentArrays . size ( ) = = 0 ) {
createDefaultArray ( ) ;
}
//enable importing of time stamped hydrophone and streamer data if in viewer mode.
if ( isViewer ) {
2023-10-09 13:56:12 +00:00
hydrophoneImportManager = new ImportDataSystem < Hydrophone > ( new HydrophoneImport ( hydrophonesProcess . getHydrophoneDataBlock ( ) ) ) ;
2022-01-07 10:41:38 +00:00
hydrophoneImportManager . setName ( " Hydrophone Data Import " ) ;
streamerImportManager = new ImportDataSystem < ArrayList < Double > > ( new StreamerImport ( hydrophonesProcess . getStreamerDataBlock ( ) ) ) ;
streamerImportManager . setName ( " Streamer Data Import " ) ;
}
// need to do a bit more to initialise the current array !
setCurrentArray ( recentArrays . get ( 0 ) ) ;
UserDisplayControl . addUserDisplayProvider ( new ArraySensorPanelProvider ( ) ) ;
TDDataProviderRegisterFX . getInstance ( ) . registerDataInfo ( new ArrayPlotProviderFX ( getStreamerDatabBlock ( ) ) ) ;
}
private void createDefaultArray ( ) {
addArray ( PamArray . createSimpleArray ( " Basic Linear Array " , 0 , - 5 , 3 , 2 , DEFAULT_HYDROPHONE_SENSITIVITY , DEFAULT_PREAMP_GAIN , new double [ ] { 0 , 20000 } ) ) ;
}
@Override
public PamObserver getObserverObject ( ) {
return this ;
}
public static ArrayManager getArrayManager ( ) {
if ( singleInstance = = null ) {
singleInstance = new ArrayManager ( null ) ;
singleInstance . addModuleInfo ( ) ; //needed for FX
PamController . getInstance ( ) . addControlledUnit ( singleInstance ) ;
}
return singleInstance ;
}
private boolean initComplete = false ;
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public void notifyModelChanged ( int changeType ) {
2024-08-27 11:48:16 +00:00
if ( changeType = = PamControllerInterface . INITIALIZATION_COMPLETE ) {
2022-01-07 10:41:38 +00:00
initComplete = true ;
}
getCurrentArray ( ) . notifyModelChanged ( changeType , initComplete ) ;
2024-08-27 11:48:16 +00:00
if ( changeType = = PamControllerInterface . INITIALIZATION_COMPLETE ) {
2022-01-07 10:41:38 +00:00
// create data units and save - is this needed in the viewer ?
// if (PamController.getInstance().getRunMode() == PamController.RUN_NORMAL) {
hydrophonesProcess . createArrayData ( ) ;
// }
}
2024-08-27 11:48:16 +00:00
if ( changeType = = PamControllerInterface . OFFLINE_DATA_LOADED ) {
2022-01-07 10:41:38 +00:00
if ( isViewer ) {
getHydrophoneDataBlock ( ) . clearChannelIterators ( ) ;
}
}
2024-08-27 11:48:16 +00:00
if ( changeType = = PamControllerInterface . HYDROPHONE_ARRAY_CHANGED ) {
2022-01-07 10:41:38 +00:00
if ( isViewer ) {
getHydrophoneDataBlock ( ) . clearChannelIterators ( ) ;
}
}
2024-08-27 11:48:16 +00:00
if ( changeType = = PamControllerInterface . GLOBAL_MEDIUM_UPDATE ) {
2022-01-07 10:41:38 +00:00
this . getCurrentArray ( ) . setSpeedOfSound ( this . getPamController ( ) . getGlobalMediumManager ( ) . getDefaultSoundSpeed ( ) ) ;
this . getCurrentArray ( ) . setDefaultSensitivity ( this . getPamController ( ) . getGlobalMediumManager ( ) . getDefaultRecieverSens ( ) ) ;
}
}
/ * *
* @return Returns the arrayFileType .
* /
public static String getArrayFileType ( ) {
return arrayFileType ;
}
/ * *
* @return Returns the recentArrays .
* /
public ArrayList < PamArray > getRecentArrays ( ) {
return recentArrays ;
}
public int getArrayCount ( ) {
if ( recentArrays = = null | | recentArrays . size ( ) = = 0 ) return 0 ;
return recentArrays . size ( ) ;
}
public void showArrayDialog ( Frame parentFrame ) {
PamArray selectedArray = ArrayDialog . showDialog ( parentFrame , singleInstance ) ;
//WARNING: need to make sure that the notify model changed is at the end of this function. It must be called after all hydrophone data loading
//and saving has occured, otherwise we don't clear channel iterators from the hydrophone dateblock and end up with concurrent modification exceptions in
//localiser modules.
if ( selectedArray ! = null ) {
hydrophonesProcess . createArrayData ( ) ;
// need to tell all modules that the array may have changed.
2024-08-27 11:48:16 +00:00
PamController . getInstance ( ) . notifyModelChanged ( PamControllerInterface . HYDROPHONE_ARRAY_CHANGED ) ;
2022-01-07 10:41:38 +00:00
}
}
public void addArray ( PamArray newArray ) {
if ( recentArrays = = null ) {
recentArrays = new ArrayList < PamArray > ( ) ;
}
recentArrays . add ( newArray ) ;
}
public void setCurrentArray ( PamArray array ) {
if ( array = = null ) return ;
recentArrays . remove ( array ) ;
array . setArrayShape ( this . getArrayShape ( array ) ) ;
array . getHydrophoneLocator ( ) ;
recentArrays . add ( 0 , array ) ;
}
public boolean removeArray ( PamArray newArray ) {
if ( recentArrays = = null ) return false ;
return recentArrays . remove ( newArray ) ;
}
/ * ( non - Javadoc )
* @see PamController . PamSettings # GetSettingsReference ( )
* /
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public Serializable getSettingsReference ( ) {
/ *
* Save the entire array classes in the serialised file
* but there will still be facilities for storing individual
* arrays in their own files for backup and export
* /
if ( recentArrays = = null ) {
return null ;
}
for ( PamArray array : recentArrays ) {
array . prepareToSerialize ( ) ;
}
return new ArrayParameters ( recentArrays ) ;
}
/ * ( non - Javadoc )
* @see PamController . PamSettings # GetSettingsVersion ( )
* /
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public long getSettingsVersion ( ) {
return ArrayParameters . serialVersionUID ;
}
/ * ( non - Javadoc )
* @see PamController . PamSettings # GetUnitName ( )
* /
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public String getUnitName ( ) {
return " Array Manager " ;
}
/ * ( non - Javadoc )
* @see PamController . PamSettings # getUnitType ( )
* /
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public String getUnitType ( ) {
return " Array Manager " ;
}
/ * ( non - Javadoc )
* @see PamController . PamSettings # RestoreSettings ( PamController . PamControlledUnitSettings )
* /
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public boolean restoreSettings ( PamControlledUnitSettings pamControlledUnitSettings ) {
try {
if ( pamControlledUnitSettings . getSettings ( ) ! = null ) {
ArrayList < PamArray > oldArrays ;
Object settings = pamControlledUnitSettings . getSettings ( ) ;
if ( settings instanceof ArrayParameters ) {
oldArrays = ( ( ArrayParameters ) settings ) . getArrayList ( ) ;
}
else {
oldArrays = ( ArrayList < PamArray > ) pamControlledUnitSettings . getSettings ( ) ;
}
/ * *
* Need to make a hard copy so that the cloning initiates the depth inversion fix !
* /
recentArrays = new ArrayList < PamArray > ( ) ;
for ( int i = 0 ; i < oldArrays . size ( ) ; i + + ) {
recentArrays . add ( oldArrays . get ( i ) . clone ( ) ) ;
}
for ( PamArray array : recentArrays ) {
array . arrayDeserialized ( ) ;
}
}
}
catch ( Exception Ex ) {
Ex . printStackTrace ( ) ;
return false ;
}
return true ;
}
/ * *
* @return Returns the currentArray .
* /
public PamArray getCurrentArray ( ) {
if ( recentArrays = = null | | recentArrays . size ( ) = = 0 ) {
createDefaultArray ( ) ;
if ( recentArrays = = null | | recentArrays . size ( ) = = 0 ) {
return null ;
}
}
return recentArrays . get ( 0 ) ;
}
static public PamArray loadArrayFromFile ( String fileName ) {
try {
ObjectInputStream file = new ObjectInputStream ( new FileInputStream ( fileName ) ) ;
PamArray array = ( PamArray ) file . readObject ( ) ;
return array ;
} catch ( Exception Ex ) {
Ex . printStackTrace ( ) ;
return null ;
}
}
static public boolean saveArrayToFile ( PamArray array ) {
// should put some stuff in so that if arrayFile is null, it asks for a name
if ( array . getArrayFile ( ) = = null ) return false ;
try {
ObjectOutputStream file = new ObjectOutputStream ( new FileOutputStream ( array . getArrayFile ( ) ) ) ;
file . writeObject ( array ) ;
} catch ( Exception Ex ) {
System . out . println ( Ex ) ;
return false ;
}
return true ;
}
// /**
// * @return the depthControl
// */
// public DepthControl getDepthControl() {
// return depthControl;
// }
//
// /**
// * @param depthControl the depthControl to set
// */
// public void setDepthControl(DepthControl depthControl) {
// if (this.depthControl != null && this.depthControl != depthControl) {
// this.depthControl.getDepthDataBlock().deleteObserver(this);
// }
// this.depthControl = depthControl;
// if (depthControl != null) {
// depthControl.getDepthDataBlock().addObserver(this);
// }
// }
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public String getObserverName ( ) {
return " Array Manager " ;
}
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public long getRequiredDataHistory ( PamObservable o , Object arg ) {
// would be better to work out how long the Gps data are being kept for and do the same
return 3600 * 1000 ;
}
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public void noteNewSettings ( ) {
}
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public void removeObservable ( PamObservable o ) {
}
2024-08-27 11:48:16 +00:00
@Override
2022-01-07 10:41:38 +00:00
public void setSampleRate ( float sampleRate , boolean notify ) {
// TODO Auto-generated method stub
}
@Override
public void masterClockUpdate ( long milliSeconds , long sampleNumber ) {
// TODO Auto-generated method stub
}
@Override
public void addData ( PamObservable o , PamDataUnit arg ) {
// TODO Auto-generated method stub
}
@Override
public void updateData ( PamObservable o , PamDataUnit arg ) {
}
/ * *
* @return the type of the current array ( point , planar , line , etc )
* /
public int getArrayType ( ) {
return getArrayShape ( getCurrentArray ( ) ) ;
}
/ * *
*
* @param phones
* @return the type of a sub array of the current array ( point , planar , line , etc )
* /
public int getArrayType ( int phones ) {
return getArrayShape ( getCurrentArray ( ) , phones ) ;
}
/ * *
*
* @param array a PAMGUARD array
* @return the type of an array ( point , plane , etc )
* /
public int getArrayShape ( PamArray array ) {
if ( array = = null ) {
return ARRAY_TYPE_NONE ;
}
int nPhones = array . getHydrophoneCount ( ) ;
int phones = PamUtils . makeChannelMap ( nPhones ) ;
return getArrayShape ( array , phones ) ;
}
/ * *
*
* @param array a PAMGUARD array
* @param phones a sub array of phones in the pam array .
* @return the type of an array ( point , plane , etc )
* /
public int getArrayShape ( PamArray array , int phones ) {
if ( array = = null | | phones = = 0 ) {
return ARRAY_TYPE_NONE ;
}
phones = getSpatiallyUniquePhones ( array , phones ) ;
int nPhones = PamUtils . getNumChannels ( phones ) ;
if ( nPhones < = 1 ) {
return ARRAY_TYPE_POINT ;
}
/ * *
* Need an array of phones , but need to exclude any which
* are at the exact same place as existing ones . So for
* each phone , look back through the list and see if
* its really unique .
* /
PamVector phoneVec ;
PamVector [ ] phoneVectors = new PamVector [ nPhones ] ;
int iPhone , jPhone , iStreamer , jStreamer ;
int uniquePhones = 0 ;
boolean matches ;
for ( int i = 0 ; i < nPhones ; i + + ) {
iPhone = PamUtils . getNthChannel ( i , phones ) ;
iStreamer = array . getStreamerForPhone ( iPhone ) ;
phoneVec = array . getAbsHydrophoneVector ( iPhone , 0 ) ;
matches = false ;
for ( int j = 0 ; j < uniquePhones ; j + + ) {
jPhone = PamUtils . getNthChannel ( j , phones ) ;
jStreamer = array . getStreamerForPhone ( jPhone ) ;
if ( phoneVec . equals ( phoneVectors [ j ] ) & jStreamer = = iStreamer ) {
matches = true ;
break ;
}
}
if ( matches ) {
continue ;
}
phoneVectors [ uniquePhones + + ] = phoneVec ;
}
nPhones = uniquePhones ;
phoneVectors = Arrays . copyOf ( phoneVectors , nPhones ) ;
int nPairs = nPhones * ( nPhones - 1 ) / 2 ;
int zeroPoints = 0 ;
int iPair = 0 ;
PamVector [ ] pairVectors = new PamVector [ nPairs ] ;
for ( int i = 0 ; i < nPhones ; i + + ) {
for ( int j = i + 1 ; j < nPhones ; j + + ) {
pairVectors [ iPair + + ] = phoneVectors [ j ] . sub ( phoneVectors [ i ] ) ;
}
}
// now test for the various types.
if ( nPhones = = 2 ) {
return ARRAY_TYPE_LINE ;
}
// will also be a line if all interpair vectors
// are in a line.
if ( areInLine ( pairVectors ) ) {
return ARRAY_TYPE_LINE ;
}
/ *
* May be a plane !
* /
if ( nPhones = = 3 ) {
return ARRAY_TYPE_PLANE ;
}
if ( areOnPlane ( pairVectors ) ) {
return ARRAY_TYPE_PLANE ;
}
return ARRAY_TYPE_VOLUME ;
}
/ * *
* Take a list of phones and return a list of ones
* which are at unique positions .
* @param array hydrophone array
* @param phones bitmap of hydrophone numbers
* @return bitmap of hydrophones which have a unique position .
* /
public int getSpatiallyUniquePhones ( PamArray array , int phones ) {
int nPhones = PamUtils . getNumChannels ( phones ) ;
int iPhone , jPhone , iStream , jStream ;
int uniqueList = 0 ;
PamVector iVec , jVec ;
boolean unique ;
for ( int i = 0 ; i < nPhones ; i + + ) {
unique = true ;
iPhone = PamUtils . getNthChannel ( i , phones ) ;
iStream = array . getStreamerForPhone ( iPhone ) ;
iVec = array . getAbsHydrophoneVector ( iPhone , - 1 ) ;
if ( iVec = = null ) continue ;
for ( int j = i + 1 ; j < nPhones ; j + + ) {
jPhone = PamUtils . getNthChannel ( j , phones ) ;
jStream = array . getStreamerForPhone ( jPhone ) ;
jVec = array . getAbsHydrophoneVector ( jPhone , - 1 ) ;
if ( jVec = = null | | jStream ! = iStream ) continue ;
if ( iVec . equals ( jVec ) ) {
unique = false ;
}
}
if ( unique ) {
uniqueList | = ( 1 < < iPhone ) ;
}
}
return uniqueList ;
}
/ * *
* Get the principle direction vectors for the current array
* @return Array of vectors pointing along the array principle axes .
* /
public PamVector [ ] getArrayDirections ( ) {
PamArray array = getCurrentArray ( ) ;
return getArrayDirections ( array ) ;
}
/ * *
* Get the principle direction vectors for a sub set of hydrophones
* within the current array
* @param phones bitmap of hydrophones
* @return Array of vectors pointing along the array principle axes .
* /
public PamVector [ ] getArrayDirection ( int phones ) {
PamArray array = getCurrentArray ( ) ;
return getArrayDirections ( array , phones ) ;
}
/ * *
* Get a set of vectors which define the principle coordinates of an array
* @param array array
* @return array of vectors
*
* /
public PamVector [ ] getArrayDirections ( PamArray array ) {
int nPhones = array . getHydrophoneCount ( ) ;
int phones = PamUtils . makeChannelMap ( nPhones ) ;
return getArrayDirections ( array , phones ) ;
}
/ * *
* Get a set of vectors which define the principle components of an array .
* < p > For a point array , null is returned
* < p > For a line array a vector pointing along the array in the direction closest
* to the y axis
* < p > For a planar array a vector pointing as closely as possible to the y axis
* and a second vector , perpendicular to the first , obeying a right hand rule , in
* the plane will be returned .
* < p > For a volumetric array , the above + the third vector ( vec product of the other two ) .
*
*
* @param array Pamguard array
* @param phones phones included in a sub array .
* @return up to three vectors defining the components of the array .
* /
public PamVector [ ] getArrayDirections ( PamArray array , int phones ) {
phones = getSpatiallyUniquePhones ( array , phones ) ;
int nPhones = PamUtils . getNumChannels ( phones ) ;
if ( nPhones < = 0 ) {
return null ;
}
PamVector [ ] arrayVectors = getArrayVectors ( array , phones ) ;
int arrayType = getArrayShape ( array , phones ) ;
switch ( arrayType ) {
case ARRAY_TYPE_POINT :
return null ;
case ARRAY_TYPE_LINE :
return getLineArrayVector ( arrayVectors ) ;
case ARRAY_TYPE_PLANE :
return getPlaneArrayVectors ( arrayVectors ) ;
case ARRAY_TYPE_VOLUME :
return getVolumeArrayVectors ( arrayVectors ) ;
}
return null ;
}
private PamVector [ ] getLineArrayVector ( PamVector [ ] arrayVectors ) {
// we know they are in a line, so just take the first two.
PamVector [ ] vectors = new PamVector [ 1 ] ;
vectors [ 0 ] = arrayVectors [ 1 ] . sub ( arrayVectors [ 0 ] ) ;
// want to line up along the positive direction of an axis if at all
// possible.
int ax = vectors [ 0 ] . getPrincipleAxis ( ) ;
if ( vectors [ 0 ] . dotProd ( PamVector . getCartesianAxes ( ax ) ) < 0 ) {
vectors [ 0 ] = vectors [ 0 ] . times ( - 1 ) ;
}
vectors [ 0 ] = vectors [ 0 ] . getUnitVector ( ) ;
return vectors ;
}
private PamVector [ ] getPlaneArrayVectors ( PamVector [ ] arrayVectors ) {
PamVector [ ] vectors = new PamVector [ 2 ] ;
PamVector [ ] vectorPairs = PamVector . getVectorPairs ( arrayVectors ) ;
int nPairs = vectorPairs . length ;
int [ ] closestAxis = new int [ nPairs ] ;
double [ ] closestAngle = new double [ nPairs ] ;
for ( int i = 0 ; i < nPairs ; i + + ) {
closestAxis [ i ] = vectorPairs [ i ] . getPrincipleAxis ( ) ;
closestAngle [ i ] = vectorPairs [ i ] . absAngle ( PamVector . getCartesianAxes ( closestAxis [ i ] ) ) ;
}
// need to find the direction vector for the plane.
// can do this by finding any non zero vector product.
2024-08-27 11:48:16 +00:00
PamVector planePerpendicular = null ;
2022-01-07 10:41:38 +00:00
for ( int i = 0 ; i < nPairs ; i + + ) {
for ( int j = ( i + 1 ) ; j < nPairs ; j + + ) {
2024-08-27 11:48:16 +00:00
if ( ! vectorPairs [ i ] . isParallel ( vectorPairs [ j ] ) ) {
2022-01-07 10:41:38 +00:00
planePerpendicular = vectorPairs [ i ] . vecProd ( vectorPairs [ j ] ) ;
break ;
}
}
if ( planePerpendicular = = null ) {
break ;
}
}
if ( planePerpendicular = = null ) {
planePerpendicular = PamVector . getZAxis ( ) . clone ( ) ;
}
// find the closest pair to each of the three axis.
int [ ] closestPair = new int [ 3 ] ;
PamVector axis ;
double closest ;
double ang ;
for ( int ax = 0 ; ax < 3 ; ax + + ) {
closestPair [ ax ] = - 1 ;
axis = PamVector . getCartesianAxes ( ax ) ;
closest = Double . MAX_VALUE ;
for ( int i = 0 ; i < nPairs ; i + + ) {
if ( closestAxis [ i ] ! = ax ) {
continue ;
}
if ( closestAngle [ i ] < closest ) {
closest = closestAngle [ i ] ;
closestPair [ ax ] = i ;
}
}
}
// try to line up the first vector on the y axis, then x, then z
int startPair = - 1 ;
if ( closestPair [ 1 ] > = 0 ) {
startPair = closestPair [ 1 ] ;
}
else if ( closestPair [ 0 ] > = 0 ) {
startPair = closestPair [ 0 ] ;
}
else {
startPair = closestPair [ 2 ] ;
}
if ( startPair < 0 ) {
return null ;
}
vectors [ 0 ] = vectorPairs [ startPair ] ;
if ( vectors [ 0 ] . angle ( PamVector . getCartesianAxes ( closestAxis [ startPair ] ) ) > Math . PI / 2 ) {
vectors [ 0 ] = vectors [ 0 ] . times ( - 1 ) ;
}
// second vector must be perpendicular to first one and also
// to the plane perpendicular.
vectors [ 1 ] = vectors [ 0 ] . vecProd ( planePerpendicular ) ;
int closestAx = vectors [ 1 ] . getPrincipleAxis ( ) ;
if ( vectors [ 1 ] . angle ( PamVector . getCartesianAxes ( closestAx ) ) > Math . PI / 2 ) {
vectors [ 1 ] = vectors [ 1 ] . times ( - 1 ) ;
}
vectors [ 0 ] = vectors [ 0 ] . getUnitVector ( ) ;
vectors [ 1 ] = vectors [ 1 ] . getUnitVector ( ) ;
return vectors ;
}
private PamVector [ ] getVolumeArrayVectors ( PamVector [ ] arrayVectors ) {
PamVector [ ] vectors = new PamVector [ 3 ] ;
for ( int i = 0 ; i < 3 ; i + + ) {
vectors [ i ] = PamVector . getCartesianAxes ( i ) . clone ( ) ;
}
return vectors ;
}
public PamVector [ ] getArrayVectors ( PamArray array , int phones ) {
int nPhones = PamUtils . getNumChannels ( phones ) ;
PamVector [ ] arrayVectors = new PamVector [ nPhones ] ;
int iPhone ;
for ( int i = 0 ; i < nPhones ; i + + ) {
iPhone = PamUtils . getNthChannel ( i , phones ) ;
arrayVectors [ i ] = array . getAbsHydrophoneVector ( iPhone , 0 ) ;
}
return arrayVectors ;
}
public static String getArrayTypeString ( int arrayType ) {
switch ( arrayType ) {
case ARRAY_TYPE_NONE :
return " No Hydrophones " ;
case ARRAY_TYPE_POINT :
return " Point Array " ;
case ARRAY_TYPE_LINE :
return " Line Array " ;
case ARRAY_TYPE_PLANE :
return " Planar Array " ;
case ARRAY_TYPE_VOLUME :
return " Volumetric Array " ;
}
return null ;
}
/ * *
* Test to see if a load of inter - pair vectors are in line or not .
* @param pvs inter - hydrophone vectors .
* @return true if they all line up .
* /
private boolean areInLine ( PamVector [ ] pvs ) {
int nPairs = pvs . length ;
for ( int i = 0 ; i < nPairs ; i + + ) {
for ( int j = i + 1 ; j < nPairs ; j + + ) {
2024-08-27 11:48:16 +00:00
if ( ! pvs [ i ] . isInLine ( pvs [ j ] ) ) {
2022-01-07 10:41:38 +00:00
return false ;
}
}
}
return true ;
}
/ * *
* Check to see if inter pair vectors define a volume
* or just a plane .
* @param pvs array of inter phone vectors
* @return true if there is significant volume .
* /
private boolean areOnPlane ( PamVector [ ] pvs ) {
double vol = getMaxVolume ( pvs ) ;
return ( vol = = 0 ) ;
}
private double getMaxVolume ( PamVector [ ] pvs ) {
int nPairs = pvs . length ;
double maxVol = 0 ;
double vol ;
for ( int i = 0 ; i < nPairs ; i + + ) {
for ( int j = i + 1 ; j < nPairs ; j + + ) {
for ( int k = j + 1 ; k < nPairs ; k + + ) {
vol = pvs [ i ] . tripleDotProduct ( pvs [ j ] , pvs [ k ] ) ;
maxVol = Math . max ( maxVol , vol ) ;
}
}
}
return maxVol ;
}
/ * *
* Called from the network receiver to create and assign some specific hydrophone streamer within an
* array and to clone a streamer to make a new one if necessary
* @param buoyStats
* /
// @Deprecated
// public void checkBuoyHydropneStreamer(BuoyStatusDataUnit buoyStats) {
// if (buoyStats.getHydrophoneStreamer() != null) {
// // no need to do anything if it' already found it's streamer.
// return;
// }
// // check that we're using the correct hydrophone locator.
// PamArray currentArray = getCurrentArray();
// HydrophoneLocator locator = currentArray.getHydrophoneLocator();
//// if (locator == null || locator.getClass() != NetworkHydrophoneLocator.class) {
//// currentArray.setArrayType(PamArray.ARRAY_TYPE_TOWED);
//// currentArray.setArrayLocator(2);
//// }
//
// // now try to find a streamer with that buoy id.
// Streamer streamer = currentArray.findStreamer(buoyStats.getBuoyName());
// if (streamer != null) {
// buoyStats.setHydrophoneStreamer(streamer);
// return;
// }
//
// // try the first streamer - see if it's already assigned to any buoy, it not grab it.
// Streamer firstStreamer = currentArray.getStreamer(0);
// if (firstStreamer != null && firstStreamer.getBuoyId1() == null) {
// buoyStats.setHydrophoneStreamer(firstStreamer);
// return;
// }
// // else will have to clone that first streamer, hydrophones and all !!!
// int nStreamers = currentArray.getNumStreamers();
// if (firstStreamer != null) {
// streamer = new Streamer(firstStreamer, nStreamers, buoyStats.getBuoyId1());
// }
// else {
// streamer = new Streamer(nStreamers);
// }
// int streamerId = currentArray.addStreamer(streamer);
// buoyStats.setHydrophoneStreamer(streamer);
//
// // clone the hydrophones attached to that streamer.
// Hydrophone oldPhone, newPhone;
// int n = currentArray.getHydrophoneCount();
// for (int i = 0; i < n; i++) {
// oldPhone = currentArray.getHydrophone(i);
// if (oldPhone.getStreamerId() != 0) {
// continue;
// }
// newPhone = new Hydrophone(currentArray.getHydrophoneCount(), oldPhone.getX() + 10*streamerId, oldPhone.getY(), oldPhone.getZ(),
// oldPhone.getType(), oldPhone.getSensitivity(), oldPhone.getBandwidth().clone(), oldPhone.getPreampGain());
// newPhone.setdX(oldPhone.getdX());
// newPhone.setdY(oldPhone.getdY());
// newPhone.setdZ(oldPhone.getdZ());
// newPhone.setStreamerId(streamerId);
// currentArray.addHydrophone(newPhone);
// }
// }
// public ArrayDialog getArrayDialog() {
// System.out.println("static array dialog: "+singleInstance.arrayDialog);
// System.out.println("this: "+ arrayDialog);
// return arrayDialog;
// }
public HydrophoneDataBlock getHydrophoneDataBlock ( ) {
return hydrophonesProcess . getHydrophoneDataBlock ( ) ;
}
public StreamerDataBlock getStreamerDatabBlock ( ) {
return hydrophonesProcess . getStreamerDataBlock ( ) ;
}
public HydrophoneSQLLogging getHydrophoneSQLLogging ( ) {
return hydrophonesProcess . getHydrophoneSQLlogging ( ) ;
}
// public void setArrayDialog(ArrayDialog arrayDialog) {
// this.arrayDialog=arrayDialog;
// }
/ * *
* Gets the GPS data block , if there is one . If no datablock is present then returns null ;
* @return reference to the gps data block ( if there is one )
* /
public static GPSDataBlock getGPSDataBlock ( ) {
// go right to the main gps data block for this,
GPSControl gpsControl = ( GPSControl ) PamController . getInstance ( ) . findControlledUnit ( GPSControl . gpsUnitType ) ;
if ( gpsControl = = null ) {
return null ;
}
return gpsControl . getGpsDataBlock ( ) ;
}
public void showHydrophoneImportDialog ( JFrame guiFrame ) {
hydrophoneImportManager . showImportDialog ( ) ;
}
public void showStreamerImportDialog ( JFrame guiFrame ) {
streamerImportManager . showImportDialog ( ) ;
}
// /**
// * Get the current geometry for a data unit's hydrophones.
// * @param dataUnits list of data units
// * @return current geometry.
// */
// public SnapshotGeometry getSnapshotGeometry(PamDataUnit dataUnit) {
// /**
// * first work out the total number of channels involved
// * and try to individually link data units back to the
// * acquisition process, just in case this ever gets used
// * with different daq's (I don't think this is even possible
// * but might as well allow for it).
// * Aiming to end up with an array of channels and an array
// * of hydrophone numbers.
// * PhoneNo's will end up with a -1 if no hydrophone is assigned
// * to a channel (again, this should never happen).
// */
// int nChan = PamUtils.getNumChannels(dataUnit.getChannelBitmap());
// int[] phoneNos = new int[nChan];
// int[] chanNos = new int[nChan];
// int iC = 0;
// int chMap = dataUnit.getChannelBitmap();
// int phoneMap = chMap;
// PamProcess sourceProcess = dataUnit.getParentDataBlock().getSourceProcess();
// if (sourceProcess instanceof AcquisitionProcess) {
// AcquisitionProcess ap = (AcquisitionProcess) sourceProcess;
// phoneMap = ap.getAcquisitionControl().ChannelsToHydrophones(chMap);
// }
// int uChans = PamUtils.getNumChannels(chMap);
// for (int c = 0; c < uChans; c++) {
// chanNos[iC] = PamUtils.getNthChannel(c, chMap);
// phoneNos[iC] = PamUtils.getNthChannel(c, phoneMap);
// iC++;
// }
// long now = dataUnit.getTimeMilliseconds();
// /*
// * Now get the array geometry for that list of hydrophones.
// * Annoyingly, a lot of the geometry functions are based around
// * phone bitmaps. however we need to be a bit wary of them since there
// * is a small chance that a hydrophone may get repeated somehow.
// */
// PamArray currentArray = getCurrentArray();
// double[][] geometry = new double[nChan][];
// double[] centre = new double[3];
// int nGood = 0;
// GpsData referenceLatLong = currentArray.getHydrophoneLocator().getReferenceLatLong(now);
//
// // arrayHeading = currentArray.getHydrophoneLocator().get
// for (int i = 0; i < nChan; i++) {
// if (phoneNos[i] >= 0) {
// geometry[i] = currentArray.getAbsHydrophoneVector(phoneNos[i], now).getVector();
// if (geometry[i] != null) {
// nGood++;
// for (int p = 0; p < 3; p++) {
// centre[p] += geometry[i][p];
// }
// }
// }
// }
// if (nGood > 0) {
// for (int p = 0; p < 3; p++) {
// centre[p] /= nGood;
// }
// }
//
// SnapshotGeometry sg = new SnapshotGeometry(currentArray, now, chanNos, phoneNos, referenceLatLong, centre, geometry);
// return sg;
// }
/ * *
* Make an ordered set of geometry data for the given hydrophones .
* data rows will be null if a hydrophone isn ' t used .
*
* Geometry is all returned in cartesian coordinates relative to
* the reference point which is the position of the first hydrophone streamer .
*
* All individual hydrophones have their pitch , roll and everything else added
* within this function .
*
* @param hydrophoneMap bitmap of used hydrophones
* @return geometry data .
* /
public SnapshotGeometry getSnapshotGeometry ( int hydrophoneMap , long timeMillis ) {
2024-03-12 13:44:24 +00:00
2022-01-07 10:41:38 +00:00
PamArray currentArray = getCurrentArray ( ) ;
if ( currentArray = = null ) {
return null ;
}
boolean getSpacingErrors = true ;
int nPhones = PamUtils . getNumChannels ( hydrophoneMap ) ;
int maxPhone = PamUtils . getHighestChannel ( hydrophoneMap ) ;
int [ ] hydrophoneList = new int [ nPhones ] ;
int [ ] streamerList = new int [ maxPhone + 1 ] ;
PamVector [ ] geometry = new PamVector [ maxPhone + 1 ] ;
PamVector [ ] streamerError = new PamVector [ maxPhone + 1 ] ;
PamVector [ ] hydrophoneError = new PamVector [ maxPhone + 1 ] ;
double [ ] centre = new double [ 3 ] ;
// GpsData referenceLatLong = currentArray.getHydrophoneLocator().getReferenceLatLong(timeMillis);
int nGood = 0 ;
int lastStreamerId = - 1 ;
GpsData firstStreamerPos = null , streamerPos = null ;
PamQuaternion streamerQuaternion = null ;
double streamerHead = 0 , streamerPitch = 0 , streamerRoll = 0 ;
PamVector streamerOffestFromFirst = null ;
PamVector streamerErrorVec = null ;
for ( int i = 0 ; i < = maxPhone ; i + + ) {
if ( ( 1 < < i & hydrophoneMap ) = = 0 ) {
continue ;
}
int streamerId = currentArray . getStreamerForPhone ( i ) ;
if ( streamerId ! = lastStreamerId ) {
lastStreamerId = streamerId ;
Streamer streamer = currentArray . getStreamer ( streamerId ) ;
streamerPos = streamer . getHydrophoneLocator ( ) . getStreamerLatLong ( timeMillis ) ; // getReferenceLatLong(timeMillis);
if ( streamerPos = = null ) {
return null ;
}
streamerErrorVec = streamer . getErrorVector ( ) ;
streamerHead = streamerPos . getHeading ( ) ;
streamerPitch = streamerPos . getPitch ( ) ;
streamerRoll = streamerPos . getRoll ( ) ;
// System.out.printf("HPR = %5.1f, %5.1f, %5.1f\n", streamerHead, streamerPitch, streamerRoll);
if ( streamerPitch ! = 0 . | | streamerRoll ! = 0 . ) {
streamerQuaternion = new PamQuaternion ( Math . toRadians ( streamerHead ) , Math . toRadians ( streamerPitch ) , Math . toRadians ( streamerRoll ) ) ;
streamerErrorVec = PamVector . rotateVector ( streamerErrorVec , streamerQuaternion ) ;
}
else {
streamerQuaternion = null ;
streamerErrorVec = streamerErrorVec . rotate ( - Math . toRadians ( streamerHead ) ) ;
}
if ( firstStreamerPos = = null ) {
firstStreamerPos = streamerPos ;
}
else {
// work out the difference in position from this streamer to the
// first one and use that as an offset. ....
streamerOffestFromFirst = new PamVector ( firstStreamerPos . distanceTo ( streamerPos ) ) ;
}
}
/ * *
* Rotate the hydrophone about the centre of it ' s streamer .
* /
2023-10-23 09:38:06 +00:00
// Hydrophone hydrophone = currentArray.getHiddenHydrophone(i);
Hydrophone hydrophone = currentArray . getHydrophone ( i , timeMillis ) ;
2022-01-07 10:41:38 +00:00
if ( hydrophone = = null ) {
continue ;
}
PamVector hydrophoneVec = hydrophone . getVector ( ) ;
PamVector hydrophoneErrorVec = hydrophone . getErrorVector ( ) ;
2023-10-23 09:38:06 +00:00
2022-01-07 10:41:38 +00:00
if ( streamerQuaternion ! = null ) {
hydrophoneVec = PamVector . rotateVector ( hydrophoneVec , streamerQuaternion ) ;
hydrophoneErrorVec = PamVector . rotateVector ( hydrophoneErrorVec , streamerQuaternion ) ;
}
else if ( streamerHead ! = 0 ) {
hydrophoneVec = hydrophoneVec . rotate ( - Math . toRadians ( streamerHead ) ) ;
hydrophoneErrorVec = hydrophoneErrorVec . rotate ( - Math . toRadians ( streamerHead ) ) ;
}
if ( streamerOffestFromFirst ! = null ) {
hydrophoneVec = hydrophoneVec . add ( streamerOffestFromFirst ) ;
}
geometry [ i ] = hydrophoneVec ;
hydrophoneList [ nGood ] = i ;
streamerList [ i ] = lastStreamerId ;
hydrophoneError [ i ] = hydrophoneErrorVec ;
streamerError [ i ] = streamerErrorVec ;
nGood + + ;
for ( int p = 0 ; p < 3 ; p + + ) {
centre [ p ] + = geometry [ i ] . getCoordinate ( p ) ;
}
}
2024-03-12 13:44:24 +00:00
if ( nGood > 0 ) {
for ( int p = 0 ; p < 3 ; p + + ) {
centre [ p ] / = nGood ;
}
return new SnapshotGeometry ( currentArray , timeMillis , streamerList , hydrophoneList , firstStreamerPos ,
new PamVector ( centre ) , geometry , streamerError , hydrophoneError ) ;
2022-01-07 10:41:38 +00:00
}
2024-03-12 13:44:24 +00:00
else {
return getMasterReferenceGeometry ( timeMillis ) ;
}
2022-01-07 10:41:38 +00:00
}
2024-03-12 13:44:24 +00:00
/ * *
* Create a snapshot geometry from the master reference position , which will either be the GPS data , or
* the centre point of the array . Worst case is that it ends up as 0 , 0 , 0
* @param timeMillis
* @return
* /
private SnapshotGeometry getMasterReferenceGeometry ( long timeMillis ) {
GPSControl gpsControl = GPSControl . getGpsControl ( ) ;
GpsData referencePoint = null ;
if ( gpsControl ! = null ) {
GpsDataUnit shipPos = gpsControl . getShipPosition ( timeMillis , true ) ;
if ( shipPos ! = null ) {
referencePoint = shipPos . getGpsData ( ) ;
}
}
2024-04-23 16:57:15 +00:00
if ( referencePoint = = null & & MasterReferencePoint . getFixTime ( ) ! = null & & MasterReferencePoint . getLatLong ( ) ! = null ) {
2024-03-12 13:44:24 +00:00
// running out of options, so fall back to the master reference point, interpolated (probably has zero speeed)
referencePoint = new GpsData ( MasterReferencePoint . getFixTime ( ) , MasterReferencePoint . getLatLong ( ) ) . getPredictedGPSData ( timeMillis ) ;
}
if ( referencePoint = = null ) {
return null ;
}
SnapshotGeometry snapgeom = new SnapshotGeometry ( getCurrentArray ( ) , timeMillis , null , null , referencePoint , new PamVector ( 0 , 0 , 0 ) , null , null , null ) ;
return snapgeom ;
}
2022-01-07 10:41:38 +00:00
//
// public SnapshotGeometry getSubDetectionGeometry(PamDataUnit superDataUnit) {
// /**
// * first work out the total number of channels involved
// * and try to individually link data units back to the
// * acquisition process, just in case this ever gets used
// * with different daq's (I don't think this is even possible
// * but might as well allow for it).
// * Aiming to end up with an array of channels and an array
// * of hydrophone numbers.
// * PhoneNo's will end up with a -1 if no hydrophone is assigned
// * to a channel (again, this should never happen).
// *
// * It can get a bit complicated having the hydrophones in an odd order
// * but this can .
// */
// int nChan = 0;
// int nSubs = superDataUnit.getSubDetectionsCount();
// for (int i = 0; i < nSubs; i++) {
// PamDataUnit dataUnit = superDataUnit.getSubDetection(i);
// nChan += PamUtils.getNumChannels(dataUnit.getChannelBitmap());
// }
// int[] phoneNos = new int[nChan];
// int[] chanNos = new int[nChan];
// int iC = 0;
// for (int i = 0; i < nSubs; i++) {
// PamDataUnit dataUnit = superDataUnit.getSubDetection(i);
// int chMap = dataUnit.getChannelBitmap();
// int phoneMap = chMap;
// PamProcess sourceProcess = dataUnit.getParentDataBlock().getSourceProcess();
// if (sourceProcess instanceof AcquisitionProcess) {
// AcquisitionProcess ap = (AcquisitionProcess) sourceProcess;
// phoneMap = ap.getAcquisitionControl().ChannelsToHydrophones(chMap);
// }
// int uChans = PamUtils.getNumChannels(chMap);
// for (int c = 0; c < uChans; c++) {
// chanNos[iC] = PamUtils.getNthChannel(c, chMap);
// phoneNos[iC] = PamUtils.getNthChannel(c, phoneMap);
// iC++;
// }
// }
// long now = superDataUnit.getTimeMilliseconds();
// /*
// * Now get the array geometry for that list of hydrophones.
// * Annoyingly, a lot of the geometry functions are based around
// * phone bitmaps. however we need to be a bit wary of them since there
// * is a small chance that a hydrophone may get repeated somehow.
// */
// PamArray currentArray = getCurrentArray();
// double[][] geometry = new double[nChan][];
// double[] centre = new double[3];
// int nGood = 0;
// GpsData referenceLatLong = currentArray.getHydrophoneLocator().getReferenceLatLong(now);
//
// // arrayHeading = currentArray.getHydrophoneLocator().get
// for (int i = 0; i < nChan; i++) {
// if (phoneNos[i] >= 0) {
// geometry[i] = currentArray.getAbsHydrophoneVector(phoneNos[i], now).getVector();
// if (geometry[i] != null) {
// nGood++;
// for (int p = 0; p < 3; p++) {
// centre[p] += geometry[i][p];
// }
// }
// }
// }
// if (nGood > 0) {
// for (int p = 0; p < 3; p++) {
// centre[p] /= nGood;
// }
// }
//
// SnapshotGeometry sg = new SnapshotGeometry(currentArray, now, chanNos, phoneNos, referenceLatLong, centre, geometry);
// return sg;
// }
/ * *
* Add module info to the array manager . Need to do this to add icon which is used in data model .
* /
private void addModuleInfo ( ) {
//need to add module info due to fact array manager is a special case
PamModuleInfo arrayModuleInfo = new PamModuleInfo ( " ArrayManager " , " Array Manager " , ArrayManager . class ) ;
arrayModuleInfo . setCoreModule ( true ) ;
this . setPamModuleInfo ( arrayModuleInfo ) ;
}
@Override
public PamControlledUnitGUI getGUI ( int flag ) {
if ( flag = = PamGUIManager . FX ) {
if ( arrayGUIFX = = null ) {
arrayGUIFX = new ArrayGUIFX ( this ) ;
}
return arrayGUIFX ;
}
if ( flag = = PamGUIManager . SWING ) {
if ( arrayGUISwing = = null ) {
arrayGUISwing = new WrapperControlledGUISwing ( this ) ;
}
return arrayGUISwing ;
}
return null ;
}
@Override
public GpsData getReferencePosition ( long timeMillis ) {
int hMap = PamUtils . makeChannelMap ( getCurrentArray ( ) . getHydrophoneCount ( ) ) ;
SnapshotGeometry sg = getSnapshotGeometry ( hMap , timeMillis ) ;
return sg . getReferenceGPS ( ) ;
}
@Override
public String getReferenceName ( ) {
return getUnitName ( ) ;
}
@Override
public void receiveSourceNotification ( int type , Object object ) {
// don't do anything by default
}
}