» Home

  » News

  » E57 User Group

  » Downloads

  » Test Data

  » Bugs

  » Documentation

  » Feature Categories

  » Extensions

  » Example

  » Supporting Partners

  » Supporting Products

  » Consultants

  » Licenses

libE57 Best Practices

This is a list of the best practices on interupting the E2807 standard and using the libE57 library.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Click each item to expand its details.


Coordinate systems used in libE57

  • XYZ - Cartesian Coordinates
  • X, Y, Z - Points in cartesian coordinates are represented by an ordered triple (x, y, z) where x, y and z are coordinates along the X, Y, Z axis, respectively. The Z-axis is in the UP direction, the Y-axis is the northing and X-axis is the easting. The coordinate system is right-handed and is measured in meters. If a lidar scanner uses a left-handed system, the data should be converted into this right-handed definition.

    img/xyz.gif

    X,Y,Z Cartesian Coordinates

  • CYL - Cylindrical Coordinates
  • Radial (ρ), Azimuth (θ), Z - Points in cylindrical coordinates are represented by an ordered triplet (ρ,θ,z), where ρ is the radial distance (in meters), θ is the azimuth angle (in radians), and z is the height (in meters).

    The following restrictions on cylindrical coordinates are applied:

    ρ >= 0
    -π < θ <= π

    img/xyz.gif

    Radial, Azimuth, and Z Coordinates

  • RAE - Spherical Coordinates
  • Range (r), Azimuth (θ), Elevation (φ) - Points in spherical coordinates are represented by an ordered triplet ( r, θ, φ), where r is the range (in meters), θ is the azimuth angle (in radians), and φ is the elevation angle (in radians). The azimuth angle is measured as the counterclock-wise rotation of the positive X-axis about the positive Z-axis of a Cartesian reference frame.

    The following restrictions on spherical coordinates are applied:

    r >= 0
    -π < θ <= π
    -π/2 <= φ <= π/2

    img/rae.gif

    Range, Azimuth, and Elevation Coordinates

  • Conversion between XYZ and CYL Coordinates
  • The conversion between cylindrical and cartesian coordinates are accomplished through the formulas:

    x = ρ cos( θ )
    y = ρ sin( θ )
    z = z

    ρ = √ x2 + y2
    θ = arctan2( y, x )
    z = z

  • Conversion between XYZ and RAE Coordinates
  • The conversion between spherical and cartesian coordinates are accomplished through the formulas:

    x = r cos( φ ) cos( θ )
    y = r cos( φ ) sin( θ )
    z = r sin( φ )

    r = √ x2 + y2 + z2
    θ = arctan2( y, x )
    φ = arcsin( z / r )

  • Scanner Coordinate System
  • The Scanner's coordinate system is defined by the manufacturer of the scanner. The Pose/Translation vector is the position of the scanner's nodal point (origin) in the World Coordinate System of the E57 file.

    img/scanner.gif

    Scanner Coordinate System


E57 Writer Best Practices

  • IndexBounds and Structured Point Clouds
  • The standard states that the bounds are defined to be tight. In table 28, the standard defines rowMaximum to be "The maximum rowIndex value of any point represented by this IndexBounds object." The problem with this is that it doesn't meet the real world needs for structured point clouds.

    One of the best ways to judge a standard is its ability to round-trip the data completely. Given a PTX (size 100,100) file of a scan where rows 92 to 100 were all invalid, converting this to E57 and dropping out all of the invalid points and converting it back to PTX (size 91,100) would NOT reproduce the identical file. It would be short rows 92 to 100. The current standard fails the test.

    The best way to solve this problem is to set the IndexBounds to the grid size of the structured point cloud like this:

    // SimpleAPI

    e57::Data3D header; ... header.indexBounds.rowMaximum = nSizeRows - 1; header.indexBounds.rowMinimum = 0; header.indexBounds.columnMaximum = nSizeColumns - 1; header.indexBounds.columnMinimum = 0; header.indexBounds.returnMaximum = nSizeReturns - 1; header.indexBounds.returnMinimum = 0; ...

    // FoundationAPI

    StructureNode indexBounds = StructureNode(imf_); indexBounds.set("rowMinimum", IntegerNode(imf_, 0 )); indexBounds.set("rowMaximum", IntegerNode(imf_, nSizeRows - 1)); indexBounds.set("columnMinimum", IntegerNode(imf_, 0 )); indexBounds.set("columnMaximum", IntegerNode(imf_, nSizeColumns - 1)); indexBounds.set("returnMinimum", IntegerNode(imf_, 0 )); indexBounds.set("returnMaximum", IntegerNode(imf_, nSizeReturns - 1)); scan.set("indexBounds", indexBounds);
  • Converting Matrix to Quaternion
  • Creating a quaternion from a rotation matrix:

    	double mat[3][3];	//matrix
    	double w,x,y,z;		//quaternion
    ...	
    	double dTrace = mat[0][0] + mat[1][1] + mat[2][2] + 1.0;
    	if(dTrace > 0.000001)
    	{
    		double S = 2.0 * sqrt(dTrace);
    		x = (mat[2][1] - mat[1][2]) / S;
    		y = (mat[0][2] - mat[2][0]) / S;
    		z = (mat[1][0] - mat[0][1]) / S;
    		w = 0.25 * S;	
    	}
    	else if((mat[0][0] > mat[1][1]) && (mat[0][0] > mat[2][2]))
    	{
    		double S = sqrt(1.0 + mat[0][0] - mat[1][1] - mat[2][2]) * 2.0;
    		x = 0.25 * S;
    		y = (mat[1][0] + mat[0][1]) / S;
    		z = (mat[0][2] + mat[2][0]) / S;
    		w = (mat[2][1] - mat[1][2]) / S;
    	}
    	else if(mat[1][1] > mat[2][2])
    	{
    		double S = sqrt(1.0 + mat[1][1] - mat[0][0] - mat[2][2]) * 2.0;
    		x = (mat[1][0] + mat[0][1]) / S;
    		y = 0.25 * S;
    		z = (mat[2][1] + mat[1][2]) / S;
    		w = (mat[0][2] - mat[2][0]) / S;
    	}
    	else
    	{
    		double S = sqrt(1.0 + mat[2][2] - mat[0][0] - mat[1][1]) * 2.0;
    		x = (mat[0][2] + mat[2][0]) / S;
    		y = (mat[2][1] + mat[1][2]) / S;
    		z = 0.25 * S;
    		w = (mat[1][0] - mat[0][1]) / S;
    	}
    	
    // normalize the quaternion if the matrix is not a clean rigid body matrix or if it has scaler information.
    
    	double len = sqrt( w*w + x*x + y*y + z*z);
    	if(len != 0.)
    	{	
    		w /= len;
    		x /= len;
    		y /= len;
    		z /= len;
    	}
    	
    

    // SimpleAPI

    header.pose.rotation.w = w; header.pose.rotation.x = x; header.pose.rotation.y = y; header.pose.rotation.z = z;

    // FoundationAPI

    if( (w != 1.) || (x != 0.) || (y != 0.) || (z != 0.) ) { StructureNode pose = StructureNode(imf_); StructureNode rotation = StructureNode(imf_); rotation.set("w", FloatNode(imf_, w)); rotation.set("x", FloatNode(imf_, x)); rotation.set("y", FloatNode(imf_, y)); rotation.set("z", FloatNode(imf_, z)); pose.set("rotation", rotation); scan.set("pose", pose); }
  • Writing Guids
  • If you choose to use Window's GUID as the E57 guid, use the following:

    //create a string like "{6A91E935-5559-477b-BE0C-7CE4E0BDFB7C}"
    
    #if defined(_MSC_VER) 	// Only for windows
    //create guid
    	GUID		guid;
    	CoCreateGuid((GUID*)&guid);
    	
    //convert to string
    	OLECHAR wbuffer[64];
    	StringFromGUID2(guid,&wbuffer[0],64); 
    	
    //converts wchar to char
    	size_t	converted = 0;
    	char	strGuid[64];
    	wcstombs_s(&converted, strGuid, wbuffer, 64);
    	
    

    // SimpleAPI

    header.guid = (char*) strGuid;

    // FoundationAPI

    root_.set("guid", StringNode(imf_, (char*) strGuid)); #else // Non-windows # include "boost/uuid/uuid.hpp" # include "boost/uuid/uuid_generators.hpp" # include "boost/uuid/uuid_io.hpp" //create uuid boost::uuids::random_generator gen; boost::uuids::uuid Uuid = gen(); //convert to string std::stringstream ss; ss << Uuid; //add {} std::string strUuid = '{'; strUuid.append(ss.str()); strUuid.append(1, '}');

    // SimpleAPI

    header.guid = strUuid.c_str();

    // FoundationAPI

    root_.set("guid", StringNode(imf_, strUuid.c_str())); #endif
  • Writing GPS Date Time
  • Use this code to write the GPS Date Time fields.

    // SimpleAPI

    #if defined(_MSC_VER) // Only for windows // Init SYSTEMTIME with the current time SYSTEMTIME fileDateTime; header.acquisitionStart.SetSystemTime(fileDateTime); #else // Init these with a UTC time int year, month, day, hour, minute; float seconds; header.acquisitionStart.SetUTCDateTime(year, month, day, hour, minute, seconds); #endif

    // FoundationAPI

    #ifdef _C_TIMECONV_H_ // Init these with a UTC time unsigned short utc_year; //Universal Time Coordinated [year] unsigned char utc_month; //1-12 months unsigned char utc_day; //1-31 days unsigned char utc_hour; //hours unsigned char utc_minute; //minutes float utc_seconds;//seconds unsigned short gps_week; //GPS week (0-1024+) double gps_tow; //GPS time of week(0-604800.0) seconds bool result = TIMECONV_GetGPSTimeFromUTCTime( &utc_year, &utc_month, &utc_day, &utc_hour, &utc_minute, &utc_seconds, &gps_week, &gps_tow); double dateTimeValue = (gps_week * 604800.) + gps_tow; int isAtomicClockReferenced = 0; #endif StructureNode acquisitionStart = StructureNode(imf_); acquisitionStart.set("dateTimeValue", FloatNode(imf_, dateTimeValue)); acquisitionStart.set("isAtomicClockReferenced", IntegerNode(imf_, isAtomicClockReferenced)); scan.set("acquisitionStart", acquisitionStart);
  • Writing Scaled Integers
  • Use this code to setup scaled integer point data.

    //choose a scale factor for the data
    	#define DATA_SCALE_FACTOR	(.000001)		
    //minimum negative limit of the data
    	double minData;					
    //maximum positive limit of the data
    	double maxData;					
    
    

    // SimpleAPI

    // check for the case where abs(minData) > maxData double maxDist = abs(maxData) > abs(minData) ? abs(maxData) : abs(minData); long maxRange = NextHigherPowerOfTwo((maxData - minData)/DATA_SCALE_FACTOR) - 1; // Calculate the needed range header.pointFields.pointRangeMinimum = ((double) -(maxRange + 1)) * DATA_SCALE_FACTOR; header.pointFields.pointRangeMaximum = ((double) maxRange) * DATA_SCALE_FACTOR; header.pointFields.pointRangeScaledInteger = DATA_SCALE_FACTOR;

    // FoundationAPI

    double pointRangeScale = DATA_SCALE_FACTOR; double pointRangeOffset = 0.; // check for the case where abs(minData) > maxData double maxDist = abs(maxData) > abs(minData) ? abs(maxData) : abs(minData); long maxRange = NextHigherPowerOfTwo((maxData - minData)/pointRangeScale) - 1; // Calculate the needed range double pointRangeMinimum = ((double) -(maxRange + 1)) * pointRangeScale; double pointRangeMaximum = ((double) maxRange) * pointRangeScale; // make integer int64_t intRangeMinimum = (int64_t) floor((pointRangeMinimum - pointRangeOffset)/pointRangeScale +.5); int64_t intRangeMaximum = (int64_t) floor((pointRangeMaximum - pointRangeOffset)/pointRangeScale +.5); StructureNode proto = StructureNode(imf_); proto.set("cartesianX", ScaledIntegerNode(imf_, 0, intRangeMinimum, intRangeMaximum, pointRangeScale, pointRangeOffset)); proto.set("cartesianY", ScaledIntegerNode(imf_, 0, intRangeMinimum, intRangeMaximum, pointRangeScale, pointRangeOffset)); proto.set("cartesianZ", ScaledIntegerNode(imf_, 0, intRangeMinimum, intRangeMaximum, pointRangeScale, pointRangeOffset)); ... // other point data fields VectorNode codecs = VectorNode(imf_, true); CompressedVectorNode points = CompressedVectorNode(imf_, proto, codecs); scan.set("points", points);
  • Writing Cartesian Bounds
  • The standard only allows doubles to be used for cartesian bounds coordinates; however point data should use scaled integers to be efficient. This mismatch will cause round off errors that will be found by the validate57.exe tool. To fix this problem the bounds need to be passed through the same calculations as a scaled integer.

    //use the scale factor for the point data
    	#define DATA_SCALE_FACTOR	(.000001)
    //calculate the minimum/maximum from the point data.	
    	double MaxX,MinX,MaxY,MinY,MaxZ,MinZ;
    	
    

    // SimpleAPI

    //do this only if the point coordinate data is a scaled integer. header.cartesianBounds.xMaximum = ceil(MaxX / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.xMinimum = floor(MinX / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.yMaximum = ceil(MaxY / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.yMinimum = floor(MinY / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.zMaximum = ceil(MaxZ / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.zMinimum = floor(MinZ / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR;

    // FoundationAPI

    //do this only if the point coordinate data is a scaled integer. StructureNode bbox = StructureNode(imf_); bbox.set("xMinimum", FloatNode(imf_, floor(MinX / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("xMaximum", FloatNode(imf_, ceil(MaxX / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("yMinimum", FloatNode(imf_, floor(MinY / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("yMaximum", FloatNode(imf_, ceil(MaxY / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("zMinimum", FloatNode(imf_, floor(MinZ / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("zMaximum", FloatNode(imf_, ceil(MaxZ / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR)); scan.set("cartesianBounds", bbox);
  • Writing Spherical Bounds
  • The standard only allows doubles to be used for spherical bounds coordinates; however range point data should use scaled integers to be efficient. This mismatch will cause round off errors that will be found by the validate57.exe tool. To fix this problem the bounds need to be passed through the same calculations as a scaled integer.

    //use the scale factor for the point data
    	#define DATA_SCALE_FACTOR	(.000001)
    //calculate the minimum/maximum from the point data.	
    	double MaxRange,MinRange,AzimuthStart,AzimuthEnd,MaxElevation,MinElevation;
    	
    

    // SimpleAPI

    //do this only if the point range coordinate data is a scaled integer. header.sphericalBounds.rangeMaximum = ceil( MaxRange / DATA_SCALE_FACTOR +0.9999999) * DATA_SCALE_FACTOR; header.sphericalBounds.rangeMinimum = floor( MinRange / DATA_SCALE_FACTOR -0.9999999) * DATA_SCALE_FACTOR; header.sphericalBounds.azimuthEnd = AzimuthEnd; header.sphericalBounds.azimuthStart = AzimuthStart; header.sphericalBounds.elevationMaximum = MaxElevation; header.sphericalBounds.elevationMinimum = MinElevation;

    // FoundationAPI

    //do this only if the point coordinate data is a scaled integer. StructureNode sbox = StructureNode(imf_); sbox.set("rangeMinimum", FloatNode(imf_, floor( MinRange / DATA_SCALE_FACTOR -0.9999999) * DATA_SCALE_FACTOR)); sbox.set("rangeMaximum", FloatNode(imf_, ceil( MaxRange / DATA_SCALE_FACTOR +0.9999999) * DATA_SCALE_FACTOR)); sbox.set("elevationMinimum", FloatNode(imf_, MinElevation)); sbox.set("elevationMaximum", FloatNode(imf_, MaxElevation)); sbox.set("azimuthStart", FloatNode(imf_, AzimuthStart)); sbox.set("azimuthEnd", FloatNode(imf_, AzimuthEnd)); scan.set("sphericalBounds", sbox);

E57 Reader Best Practices

  • Using IntensityLimits
  • Since the intensity unit is unspecified it is necessary to normalize it into a known range before converting it into other usable forms.

    // SimpleAPI

    double intOffset = header.intensityLimits.intensityMinimum; double intRange = header.intensityLimits.intensityMaximum - intOffset; if(intRange <= 0.) intRange = 1.;

    // FoundationAPI

    double intensityMinimum = 0.; double intensityMaximum = 1.; if(scan.isDefined("intensityLimits")) { StructureNode intbox(scan.get("intensityLimits")); if( intbox.get("intensityMaximum").type() == E57_SCALED_INTEGER ) { intensityMaximum = (double) ScaledIntegerNode(intbox.get("intensityMaximum")).scaledValue(); intensityMinimum = (double) ScaledIntegerNode(intbox.get("intensityMinimum")).scaledValue(); } else if( intbox.get("intensityMaximum").type() == E57_FLOAT ){ intensityMaximum = FloatNode(intbox.get("intensityMaximum")).value(); intensityMinimum = FloatNode(intbox.get("intensityMinimum")).value(); } else if( intbox.get("intensityMaximum").type() == E57_INTEGER) { intensityMaximum = (double) IntegerNode(intbox.get("intensityMaximum")).value(); intensityMinimum = (double) IntegerNode(intbox.get("intensityMinimum")).value(); } } double intOffset = intensityMinimum; double intRange = intensityMaximum - intOffset; if(intRange <= 0.) intRange = 1.; ... //for each point this converts intensity data into 0. to 1. range double intensity = (intensityData[i] - intOffset) / intRange;
  • Using ColorLimits
  • Since the color unit is unspecified it is necessary to convert it into a known range before using it.

    // SimpleAPI

    double redOffset = header.colorLimits.colorRedMinimum; double redRange = header.colorLimits.colorRedMaximum - redOffset; if(redRange <= 0.) redRange = 1.; double greenOffset = header.colorLimits.colorGreenMinimum; double greenRange = header.colorLimits.colorGreenMaximum - greenOffset; if(greenRange <= 0.) greenRange = 1.; double blueOffset = header.colorLimits.colorBlueMinimum; double blueRange = header.colorLimits.colorBlueMaximum - blueOffset; if(blueRange <= 0.) blueRange = 1.;

    // FoundationAPI

    double colorRedMinimum = 0.; double colorRedMaximum = 0.; double colorGreenMinimum = 0.; double colorGreenMaximum = 0.; double colorBlueMinimum = 0.; double colorBlueMaximum = 0.; if(scan.isDefined("colorLimits")) { StructureNode colorbox(scan.get("colorLimits")); if( colorbox.get("colorRedMaximum").type() == E57_SCALED_INTEGER ) { colorRedMaximum = (double) ScaledIntegerNode(colorbox.get("colorRedMaximum")).scaledValue(); colorRedMinimum = (double) ScaledIntegerNode(colorbox.get("colorRedMinimum")).scaledValue(); colorGreenMaximum = (double) ScaledIntegerNode(colorbox.get("colorGreenMaximum")).scaledValue(); colorGreenMinimum = (double) ScaledIntegerNode(colorbox.get("colorGreenMinimum")).scaledValue(); colorBlueMaximum = (double) ScaledIntegerNode(colorbox.get("colorBlueMaximum")).scaledValue(); colorBlueMinimum = (double) ScaledIntegerNode(colorbox.get("colorBlueMinimum")).scaledValue(); } else if( colorbox.get("colorRedMaximum").type() == E57_FLOAT ){ colorRedMaximum = FloatNode(colorbox.get("colorRedMaximum")).value(); colorRedMinimum = FloatNode(colorbox.get("colorRedMinimum")).value(); colorGreenMaximum = FloatNode(colorbox.get("colorGreenMaximum")).value(); colorGreenMinimum = FloatNode(colorbox.get("colorGreenMinimum")).value(); colorBlueMaximum = FloatNode(colorbox.get("colorBlueMaximum")).value(); colorBlueMinimum = FloatNode(colorbox.get("colorBlueMinimum")).value(); } else if( colorbox.get("colorRedMaximum").type() == E57_INTEGER) { colorRedMaximum = (double) IntegerNode(colorbox.get("colorRedMaximum")).value(); colorRedMinimum = (double) IntegerNode(colorbox.get("colorRedMinimum")).value(); colorGreenMaximum = (double) IntegerNode(colorbox.get("colorGreenMaximum")).value(); colorGreenMinimum = (double) IntegerNode(colorbox.get("colorGreenMinimum")).value(); colorBlueMaximum = (double) IntegerNode(colorbox.get("colorBlueMaximum")).value(); colorBlueMinimum = (double) IntegerNode(colorbox.get("colorBlueMinimum")).value(); } } double redOffset = colorRedMinimum; double redRange = colorRedMaximum - redOffset; if(redRange <= 0.) redRange = 1.; double greenOffset = colorGreenMinimum; double greenRange = colorGreenMaximum - greenOffset; if(greenRange <= 0.) greenRange = 1.; double blueOffset = colorBlueMinimum; double blueRange = colorBlueMaximum - blueOffset; if(blueRange <= 0.) blueRange = 1.; ... //for each point this converts color data into 0 to 255 range UCHAR red = (UCHAR) (((redData[i] - redOffset) * 255) / redRange); UCHAR green = (UCHAR) (((greenData[i] - greenOffset) * 255) / greenRange); UCHAR blue = (UCHAR) (((blueData[i] - blueOffset) * 255) / blueRange);
  • Using IndexBounds for Structured Size
  • The structure point cloud size can be calculated using the following:

    // SimpleAPI

    int nSizeRows = header.indexBounds.rowMaximum - header.indexBounds.rowMinimum + 1; int nSizeColumns = header.indexBounds.columnMaximum - header.indexBounds.columnMinimum + 1; int nSizeReturns = header.indexBounds.returnMaximum - header.indexBounds.returnMinimum + 1;

    // FoundationAPI

    int rowMaximum = 0.; int rowMinimum = 0.; int columnMaximum = 0.; int columnMinimum = 0.; int returnMaximum = 0.; int returnMinimum = 0.; if(scan.isDefined("indexBounds")) { StructureNode ibox(scan.get("indexBounds")); if(ibox.isDefined("rowMaximum")) { rowMinimum = IntegerNode(ibox.get("rowMinimum")).value(); rowMaximum = IntegerNode(ibox.get("rowMaximum")).value(); } if(ibox.isDefined("columnMaximum")) { columnMinimum = IntegerNode(ibox.get("columnMinimum")).value(); columnMaximum = IntegerNode(ibox.get("columnMaximum")).value(); } if(ibox.isDefined("returnMaximum")) { returnMinimum = IntegerNode(ibox.get("returnMinimum")).value(); returnMaximum = IntegerNode(ibox.get("returnMaximum")).value(); } } int nSizeRows = rowMaximum - rowMinimum + 1; int nSizeColumns = columnMaximum - columnMinimum + 1; int nSizeReturns = returnMaximum - returnMinimum + 1;
  • Converting Quaternion to Matrix
  • Creating a rotation matrix from a quaternion:

    // SimpleAPI

    double w = header.pose.rotation.w; double x = header.pose.rotation.x; double y = header.pose.rotation.y; double z = header.pose.rotation.z;

    // FoundationAPI

    double w,x,y,z; if(scan.isDefined("pose")) { StructureNode pose(scan.get("pose")); if(pose.isDefined("rotation")) { StructureNode rotation(pose.get("rotation")); w = FloatNode(rotation.get("w")).value(); x = FloatNode(rotation.get("x")).value(); y = FloatNode(rotation.get("y")).value(); z = FloatNode(rotation.get("z")).value(); } } // make rotation matrix double mat[3][3]; mat[0][0] = 1. - 2.*y*y - 2.*z*z; mat[0][1] = 2.*x*y - 2.*z*w; mat[0][2] = 2.*x*z + 2.*y*w; mat[1][0] = 2.*x*y + 2.*z*w; mat[1][1] = 1. - 2.*x*x - 2.*z*z; mat[1][2] = 2.*y*z - 2.*x*w; mat[2][0] = 2.*x*z - 2.*y*w; mat[2][1] = 2.*y*z + 2.*x*w; mat[2][2] = 1. - 2.*x*x - 2.*y*y;
  • Reading Guids
  • If the E57 writer used a windows GUID then this code will recover the GUID structure from a string.

    #if defined(_MSC_VER)	// Only for windows
    	GUID guid;	//--- Here is the GUID
    	
    

    // FoundationAPI

    ustring guid = StringNode(root_.get("guid")).value(); CString strGuid = (_bstr_t) guid; //converts char to wchar

    // SimpleAPI

    CString strGuid = (_bstr_t) header.guid.c_str(); //add {} if missing CString winGuid = strGuid; if( strGuid[0] != _T('{')) { winGuid = _T("{"); winGuid += strGuid; winGuid += _T("}"); } //looking for a string like "{6A91E935-5559-477b-BE0C-7CE4E0BDFB7C}" if( winGuid.GetLength() == 38 && winGuid[0] == _T('{') && winGuid[9] == _T('-') && winGuid[14] == _T('-') && winGuid[19] == _T('-') && winGuid[24] == _T('-') && winGuid[37] == _T('}')) { //convert to GUID HRESULT hr = IIDFromString((LPOLESTR) winGuid,(LPIID) &guid); } else { ... //guid is not a windows guid } #else //Non-windows # include "boost/uuid/uuid.hpp" # include "boost/uuid/uuid_generators.hpp" # include "boost/uuid/uuid_io.hpp" boost::uuids::uuid Uuid; //--- Here is the UUID

    // FoundationAPI

    string strUuid = StringNode(root_.get("guid")).value();

    // SimpleAPI

    string strUuid = header.guid.c_str(); //remove {} if present if(strUuid[0] == '{') strUuid.erase(1,0); //looking for a string like "6A91E935-5559-477b-BE0C-7CE4E0BDFB7C" if( strUuid.length() >= 36 && strUuid[8] == '-' && strUuid[13] == '-' && strUuid[18] == '-' && strUuid[23] == '-') { //convert to uuid std::stringstream ss; ss << strUuid; ss >> Uuid; } else { ... //uuid is not a windows guid } #endif
  • Reading GPS Date Time
  • Use this code to read the GPS Date Time fields.

    // SimpleAPI

    #if defined(_MSC_VER) // Only for windows SYSTEMTIME fileDateTime; header.acquisitionStart.dateTimeValue.GetSystemTime(fileDateTime); #else int year, month, day, hour, minute; float seconds; header.acquisitionStart.dateTimeValue.GetUTCDateTime(year, month, day, hour, minute, seconds); #endif

    // FoundationAPI

    double dateTimeValue = 0.; int isAtomicClockReferenced = 0; if(scan.isDefined("acquisitionStart")) { StructureNode acquisitionStart(scan.get("acquisitionStart")); dateTimeValue = FloatNode(acquisitionStart.get("dateTimeValue")).value(); isAtomicClockReferenced = (int32_t) IntegerNode(acquisitionStart.get("isAtomicClockReferenced")).value(); } #ifdef _C_TIMECONV_H_ unsigned short utc_year; //Universal Time Coordinated [year] unsigned char utc_month; //1-12 months unsigned char utc_day; //1-31 days unsigned char utc_hour; //hours unsigned char utc_minute; //minutes float utc_seconds;//seconds unsigned short gps_week; //GPS week (0-1024+) double gps_tow; //GPS time of week(0-604800.0) seconds gps_week = ((int)floor(dateTimeValue))/604800; gps_tow = dateTimeValue - gps_week*604800.; bool result = TIMECONV_GetUTCTimeFromGPSTime( gps_week, gps_tow, &utc_year, &utc_month, &utc_day, &utc_hour, &utc_minute, &utc_seconds); #endif
  • Reading Scaled Integers
  • Use this code to recover the scaled integer point data range. However, the point data has already been converted when you get the data.

    // SimpleAPI

    double pointRangeScaledInteger = header.pointFields.pointRangeScaledInteger; double pointRangeMinimum = header.pointFields.pointRangeMinimum; double pointRangeMaximum = header.pointFields.pointRangeMaximum;

    // FoundationAPI

    double pointRangeScaledInteger = 0.; //FloatNode double pointRangeMinimum = 0.; double pointRangeMaximum = 0.; if( proto.isDefined("cartesianX")) { if( proto.get("cartesianX").type() == E57_SCALED_INTEGER ) { double scale = ScaledIntegerNode(proto.get("cartesianX")).scale(); double offset = ScaledIntegerNode(proto.get("cartesianX")).offset(); int64_t minimum = ScaledIntegerNode(proto.get("cartesianX")).minimum(); int64_t maximum = ScaledIntegerNode(proto.get("cartesianX")).maximum(); pointRangeMinimum = (double) minimum * scale + offset; pointRangeMaximum = (double) maximum * scale + offset; pointRangeScaledInteger = scale; } else if( proto.get("cartesianX").type() == E57_FLOAT ) { pointRangeMinimum = FloatNode(proto.get("cartesianX")).minimum(); pointRangeMaximum = FloatNode(proto.get("cartesianX")).maximum(); } }
  • Reading Cartesian Bounds
  • Use this code to read the CartesianBounds data.

    FoundationAPI

    double MaxX,MinX,MaxY,MinY,MaxZ,MinZ; if(scan.isDefined("cartesianBounds")) { StructureNode bbox(scan.get("cartesianBounds")); if( bbox.get("xMinimum").type() == E57_SCALED_INTEGER ) { MinX = (double) ScaledIntegerNode(bbox.get("xMinimum")).scaledValue(); MaxX = (double) ScaledIntegerNode(bbox.get("xMaximum")).scaledValue(); MinY = (double) ScaledIntegerNode(bbox.get("yMinimum")).scaledValue(); MaxY = (double) ScaledIntegerNode(bbox.get("yMaximum")).scaledValue(); MinZ = (double) ScaledIntegerNode(bbox.get("zMinimum")).scaledValue(); MaxZ = (double) ScaledIntegerNode(bbox.get("zMaximum")).scaledValue(); } else if( bbox.get("xMinimum").type() == E57_INTEGER ) { MinX = (double) IntegerNode(bbox.get("xMinimum")).value(); MaxX = (double) IntegerNode(bbox.get("xMaximum")).value(); MinY = (double) IntegerNode(bbox.get("yMinimum")).value(); MaxY = (double) IntegerNode(bbox.get("yMaximum")).value(); MinZ = (double) IntegerNode(bbox.get("zMinimum")).value(); MaxZ = (double) IntegerNode(bbox.get("zMaximum")).value(); } else if( bbox.get("xMinimum").type() == E57_FLOAT ){ MinX = FloatNode(bbox.get("xMinimum")).value(); MaxX = FloatNode(bbox.get("xMaximum")).value(); MinY = FloatNode(bbox.get("yMinimum")).value(); MaxY = FloatNode(bbox.get("yMaximum")).value(); MinZ = FloatNode(bbox.get("zMinimum")).value(); MaxZ = FloatNode(bbox.get("zMaximum")).value(); } }

    SimpleAPI

    double MinX = header.cartesianBounds.xMinimum; double MaxX = header.cartesianBounds.xMaximum; double MinY = header.cartesianBounds.yMinimum; double MaxY = header.cartesianBounds.yMaximum; double MinZ = header.cartesianBounds.zMinimum; double MaxZ = header.cartesianBounds.zMaximum;
  • Reading Spherical Bounds
  • Use this code to read the SphericalBounds data.

    FoundationAPI

    double MaxRange,MinRange,AzimuthStart,AzimuthEnd,MaxElevation,MinElevation; if(scan.isDefined("sphericalBounds")) { StructureNode sbox(scan.get("sphericalBounds")); if( sbox.get("rangeMinimum").type() == E57_SCALED_INTEGER ) { MinRange = (double) ScaledIntegerNode(sbox.get("rangeMinimum")).scaledValue(); MaxRange = (double) ScaledIntegerNode(sbox.get("rangeMaximum")).scaledValue(); } else if( sbox.get("rangeMinimum").type() == E57_FLOAT ){ MinRange = FloatNode(sbox.get("rangeMinimum")).value(); MaxRange = FloatNode(sbox.get("rangeMaximum")).value(); } if( sbox.get("elevationMinimum").type() == E57_SCALED_INTEGER ) { MinElevation = (double) ScaledIntegerNode(sbox.get("elevationMinimum")).scaledValue(); MaxElevation = (double) ScaledIntegerNode(sbox.get("elevationMaximum")).scaledValue(); } else if( sbox.get("elevationMinimum").type() == E57_FLOAT ){ MinElevation = FloatNode(sbox.get("elevationMinimum")).value(); MaxElevation = FloatNode(sbox.get("elevationMaximum")).value(); } if( sbox.get("azimuthStart").type() == E57_SCALED_INTEGER ) { AzimuthStart = (double) ScaledIntegerNode(sbox.get("azimuthStart")).scaledValue(); AzimuthEnd = (double) ScaledIntegerNode(sbox.get("azimuthEnd")).scaledValue(); } else if( sbox.get("azimuthStart").type() == E57_FLOAT ){ AzimuthStart = FloatNode(sbox.get("azimuthStart")).value(); AzimuthEnd = FloatNode(sbox.get("azimuthEnd")).value(); } }

    SimpleAPI

    double MinRange = header.sphericalBounds.rangeMinimum; double MaxRange = header.sphericalBounds.rangeMaximum; double MinElevation = header.sphericalBounds.elevationMinimum; double MaxElevation = header.sphericalBounds.elevationMaximum; double AzimuthStart = header.sphericalBounds.azimuthStart; double AzimuthEnd = header.sphericalBounds.azimuthEnd;
  • Reading Return Index
  • The standard states that returnIndex is zero based. That is, 0 is the first return, 1 is the second, and so on. Shall be in the interval [0, returnCount - 1]. That means the indexBounds.returnMinimum will always be 0.

    The returnCount is the total number of returns for the pulse that is corresponds to. Shall be in the interval of [0, indexBounds.returnMaximum + 1]. This means the a returnCount of 0 is an invalid point.

    So for a scanner returning upto 3 return points, the returnIndex,returnCount would be 0,3 for the first return, 1,3 for the second return, and 2,3 for the last return. indexBounds.returnMinimum will be 0 and indexBounds.returnMaximum will be 2;

    	int nSize = 1024;  //buffer size;
    	int8_t *pReturnIndex = NULL;
    	int8_t *pReturnCount = NULL;
    

    // SimpleAPI

    int returnMaximum = 0.; int returnMinimum = 0.; if(header.pointFields.returnIndexField) { pReturnIndex = new int8_t[nSize]; returnMinimum = (int) header.indexBounds.returnMinimum; returnMaximum = (int) header.indexBounds.returnMaximum; } if(header.pointFields.returnCountField) pReturnCount = new int8_t[nSize]; ... e57::CompressedVectorReader reader = eReader->SetUpData3DPointsData( scanIndex, // data block index given by the NewData3D nSize, // size of each element buffer. pCartesianX, // pointer to a buffer with the X coordinate (in meters) of the point in Cartesian coordinates pCartesianY, // pointer to a buffer with the Y coordinate (in meters) of the point in Cartesian coordinates pCartesianZ, // pointer to a buffer with the Z coordinate (in meters) of the point in Cartesian coordinates pXYZInvalid, // Value = 0 if the point is considered valid, 1 or 2 otherwise pIntData, // pointer to a buffer with the Point response intensity. Unit is unspecified pIntInvalid, // Value = 0 if the intensity is considered valid, 1 otherwise pRedData, // pointer to a buffer with the Red color coefficient. Unit is unspecified pGreenData, // pointer to a buffer with the Green color coefficient. Unit is unspecified pBlueData, // pointer to a buffer with the Blue color coefficient. Unit is unspecified pColorInvalid, // Value = 0 if the color is considered valid, 1 otherwise pRangeData, // pointer to a buffer with the range (in meters) of points in spherical coordinates. Shall be non-negative pAzimuthData, // pointer to a buffer with the Azimuth angle (in radians) of point in spherical coordinates pElevationData, // pointer to a buffer with the Elevation angle (in radians) of point in spherical coordinates pRAEInvalid, // Value = 0 if the range is considered valid, 1 otherwise pRowIndex, // pointer to a buffer with the row number of point (zero based). // This is useful for data that is stored in a regular grid. Shall be in the interval (0, 2^63). pColumnIndex, // pointer to a buffer with the column number of point (zero based). // This is useful for data that is stored in a regular grid. Shall be in the interval (0, 2^63). pReturnIndex, // pointer to a buffer with the number of this return (zero based). // That is, 0 is the first return, 1 is the second, and so on. Shall be in the interval (0, returnCount). // Only for multi-return sensors. pReturnCount, // pointer to a buffer with the total number of returns for the pulse that this corresponds to. // Shall be in the interval (0, 2^63). Only for multi-return sensors. pTimeStamp, // pointer to a buffer with the time (in seconds) since the start time for the data, // which is given by acquisitionStart in the parent Data3D Structure. Shall be non-negative pTimeInvalid // Value = 0 if the timeStamp is considered valid, 1 otherwise ); ...

    // FoundationAPI

    StructureNode scan(data3D_.get(dataIndex)); int returnMaximum = 0.; int returnMinimum = 0.; if(scan.isDefined("indexBounds")) { StructureNode ibox(scan.get("indexBounds")); ... if(ibox.isDefined("returnMaximum")) { returnMinimum = IntegerNode(ibox.get("returnMinimum")).value(); returnMaximum = IntegerNode(ibox.get("returnMaximum")).value(); } } CompressedVectorNode points(scan.get("points")); StructureNode prototype(points.prototype()); ... int64_t protoCount = prototype.childCount(); int64_t protoIndex; vector destBuffers; for( protoIndex = 0; protoIndex < protoCount; protoIndex++) { ustring name = prototype.get(protoIndex).elementName(); ... if((name.compare("returnIndex") == 0) && prototype.isDefined("returnIndex")) { pReturnIndex = new int8_t[count]; destBuffers.push_back(SourceDestBuffer(imf_, "returnIndex", pReturnIndex, (unsigned) count, true, false)); } else if((name.compare("returnCount") == 0) && prototype.isDefined("returnCount")) { pReturnCount = new int8_t[count]; destBuffers.push_back(SourceDestBuffer(imf_, "returnCount", pReturnCount, (unsigned) count, true, false)); } ... } CompressedVectorReader reader = points.reader(destBuffers); ...

    // Both

    unsigned size = 0; while(size = reader.read()) { for(long i = 0; i < size; i++) { Point P(pCartesianX[i],pCartesianY[i],pCartesianZ[i]); if(pReturnIndex) { int ret = (pReturnIndex[i] - returnMinimum); //cannot deal with more than one return per point // use only the first return if(ret > 0) continue; //discard other returns // or use only the last return if(ret != (pReturnCount[i] - 1)) continue; //discard all others ... //use this point } ... } }






















This site is © Copyright 2010 E57.04 3D Imaging System File Format Committee, All Rights Reserved