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.
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
-π < θ <= π
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
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.
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
|