[MESH] scalar color settings depending on classification (#36313)

* [MESH] [FEATURE] Sets meh color ramp classification from metadata read by MDAL driver.
Some mesh layer formats can provide values that can be compressed by categorizing values in consecutive intervals, each represent by an integer or byte. MDAL has the capabilities to recognize this dataset type and store the bounds of each class an the units in the metadata.
QGIS uses this metadata to setup adapted color ramp shader.

* [MDAL] update to pre-release 0.5.92
This commit is contained in:
Vincent Cloarec 2020-05-11 01:19:22 -04:00 committed by GitHub
parent b6a7a10703
commit fca90a7bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 519 additions and 62 deletions

View File

@ -194,10 +194,16 @@ std::set<std::string> MDAL::Driver3Di::ignoreNetCDFVariables()
return ignore_variables; return ignore_variables;
} }
void MDAL::Driver3Di::parseNetCDFVariableMetadata( int varid, const std::string &variableName, std::string &name, bool *is_vector, bool *is_x ) void MDAL::Driver3Di::parseNetCDFVariableMetadata( int varid,
std::string &variableName,
std::string &name,
bool *is_vector,
bool *isPolar,
bool *is_x )
{ {
*is_vector = false; *is_vector = false;
*is_x = true; *is_x = true;
*isPolar = false;
std::string long_name = mNcFile->getAttrStr( "long_name", varid ); std::string long_name = mNcFile->getAttrStr( "long_name", varid );
if ( long_name.empty() ) if ( long_name.empty() )
@ -209,6 +215,7 @@ void MDAL::Driver3Di::parseNetCDFVariableMetadata( int varid, const std::string
} }
else else
{ {
variableName = standard_name;
if ( MDAL::contains( standard_name, "_x_" ) ) if ( MDAL::contains( standard_name, "_x_" ) )
{ {
*is_vector = true; *is_vector = true;
@ -228,6 +235,7 @@ void MDAL::Driver3Di::parseNetCDFVariableMetadata( int varid, const std::string
} }
else else
{ {
variableName = long_name;
if ( MDAL::contains( long_name, " in x direction" ) ) if ( MDAL::contains( long_name, " in x direction" ) )
{ {
*is_vector = true; *is_vector = true;
@ -245,3 +253,9 @@ void MDAL::Driver3Di::parseNetCDFVariableMetadata( int varid, const std::string
} }
} }
} }
std::vector<std::pair<double, double>> MDAL::Driver3Di::parseClassification( int varid ) const
{
MDAL_UNUSED( varid );
return std::vector<std::pair<double, double>>();
}

View File

@ -50,8 +50,13 @@ namespace MDAL
std::string getCoordinateSystemVariableName() override; std::string getCoordinateSystemVariableName() override;
std::string getTimeVariableName() const override; std::string getTimeVariableName() const override;
std::set<std::string> ignoreNetCDFVariables() override; std::set<std::string> ignoreNetCDFVariables() override;
void parseNetCDFVariableMetadata( int varid, const std::string &variableName, void parseNetCDFVariableMetadata( int varid,
std::string &name, bool *is_vector, bool *is_x ) override; std::string &variableName,
std::string &name,
bool *is_vector,
bool *isPolar,
bool *is_x ) override;
std::vector<std::pair<double, double>> parseClassification( int varid ) const override;
//! Returns number of vertices //! Returns number of vertices
size_t parse2DMesh(); size_t parse2DMesh();

View File

@ -6,7 +6,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <netcdf.h> #include <netcdf.h>
#include "math.h" #include <cmath>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <cstring> #include <cstring>
@ -16,6 +16,28 @@
#include "mdal_utils.hpp" #include "mdal_utils.hpp"
#include "mdal_logger.hpp" #include "mdal_logger.hpp"
static std::pair<std::string, std::string> metadataFromClassification( const MDAL::Classification &classes )
{
std::pair<std::string, std::string> classificationMeta;
classificationMeta.first = "classification";
std::string classification;
for ( const auto boundValues : classes )
{
if ( boundValues.first != NC_FILL_DOUBLE )
classification.append( MDAL::doubleToString( boundValues.first ) );
if ( boundValues.second != NC_FILL_DOUBLE )
{
classification.append( "," );
classification.append( MDAL::doubleToString( boundValues.second ) );
}
if ( boundValues != classes.back() )
classification.append( ";;" );
}
classificationMeta.second = classification;
return classificationMeta;
}
MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo() MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo()
{ {
/* /*
@ -101,63 +123,188 @@ MDAL::cfdataset_info_map MDAL::DriverCF::parseDatasetGroupInfo()
continue; continue;
// Get name, if it is vector and if it is x or y // Get name, if it is vector and if it is x or y
std::string name; std::string vectorName;
bool is_vector = true; bool is_vector = true;
bool is_polar = false;
bool is_x = false; bool is_x = false;
Classification classes = parseClassification( varid );
bool isClassified = !classes.empty();
parseNetCDFVariableMetadata( varid, variable_name, vectorName, &is_vector, &is_polar, &is_x );
parseNetCDFVariableMetadata( varid, variable_name, name, &is_vector, &is_x ); Metadata meta;
// check for units
// Add it to the map std::string units;
auto it = dsinfo_map.find( name ); try
if ( it != dsinfo_map.end() )
{ {
units = mNcFile->getAttrStr( "units", varid );
std::pair<std::string, std::string> unitMeta;
unitMeta.first = "units";
unitMeta.second = units;
meta.push_back( unitMeta );
}
catch ( MDAL::Error & )
{}
//construct classification metadata
if ( isClassified && !is_vector )
{
meta.push_back( metadataFromClassification( classes ) );
}
// Add dsinfo to the map
auto it = dsinfo_map.find( vectorName );
if ( it != dsinfo_map.end() && is_vector )
{
// this dataset is already existing and it is a vector dataset
if ( is_x ) if ( is_x )
{ {
it->second.ncid_x = varid; it->second.ncid_x = varid;
it->second.classification_x = classes;
} }
else else
{ {
it->second.ncid_y = varid; it->second.ncid_y = varid;
it->second.classification_y = classes;
} }
// If it is classified, we want to keep each component as scalar
// So create two scalar dataset groups
if ( isClassified )
{
CFDatasetGroupInfo scalarDsInfoX;
scalarDsInfoX = it->second;
scalarDsInfoX.is_vector = false;
scalarDsInfoX.is_polar = false;
CFDatasetGroupInfo scalarDsInfoY;
scalarDsInfoY = it->second;
scalarDsInfoY.is_vector = false;
scalarDsInfoX.is_polar = false;
scalarDsInfoX.ncid_x = it->second.ncid_x;
scalarDsInfoY.ncid_x = it->second.ncid_y;
if ( is_x )
{
scalarDsInfoX.name = variable_name;
scalarDsInfoX.classification_x = classes;
scalarDsInfoX.metadata = meta;
} }
else else
{
scalarDsInfoY.name = variable_name;
scalarDsInfoY.classification_x = classes;
scalarDsInfoY.metadata = meta;
}
scalarDsInfoX.metadata.push_back( metadataFromClassification( scalarDsInfoX.classification_x ) );
scalarDsInfoY.metadata.push_back( metadataFromClassification( scalarDsInfoY.classification_y ) );
dsinfo_map[scalarDsInfoX.name] = scalarDsInfoX;
dsinfo_map[scalarDsInfoY.name] = scalarDsInfoY;
}
it->second.name = vectorName;
}
else if ( it == dsinfo_map.end() || ( isClassified && is_vector ) )
{ {
CFDatasetGroupInfo dsInfo; CFDatasetGroupInfo dsInfo;
dsInfo.nTimesteps = nTimesteps; dsInfo.nTimesteps = nTimesteps;
dsInfo.is_vector = is_vector; dsInfo.ncid_x = -1;
dsInfo.ncid_y = -1;
if ( is_x ) if ( is_x )
{ {
dsInfo.ncid_x = varid; dsInfo.ncid_x = varid;
dsInfo.classification_x = classes;
} }
else else
{ {
dsInfo.ncid_y = varid; dsInfo.ncid_y = varid;
dsInfo.classification_y = classes;
} }
dsInfo.outputType = mDimensions.type( dimid ); dsInfo.outputType = mDimensions.type( dimid );
dsInfo.name = name; dsInfo.is_vector = is_vector;
dsInfo.is_polar = is_polar;
dsInfo.nValues = mDimensions.size( mDimensions.type( dimid ) ); dsInfo.nValues = mDimensions.size( mDimensions.type( dimid ) );
dsInfo.timeLocation = timeLocation; dsInfo.timeLocation = timeLocation;
dsinfo_map[name] = dsInfo; dsInfo.metadata = meta;
if ( is_vector && !isClassified )
dsInfo.name = vectorName;
else
dsInfo.name = variable_name;
dsinfo_map[vectorName] = dsInfo; //if is not vector, vectorName=variableName
} }
} }
} }
while ( true ); while ( true );
// check the dsinfo if there dataset group defined as vector without valid ncid_y
// if ncid_y<0 set the datasetinfo to scalar
for ( auto &it : dsinfo_map )
{
if ( it.second.is_vector && it.second.ncid_y < 0 )
it.second.is_vector = false;
}
return dsinfo_map; return dsinfo_map;
} }
static void populate_vals( bool is_vector, double *vals, size_t i, static void populate_vector_vals( double *vals, size_t i,
const std::vector<double> &vals_x, const std::vector<double> &vals_y, const std::vector<double> &vals_x, const std::vector<double> &vals_y,
size_t idx, double fill_val_x, double fill_val_y ) size_t idx, double fill_val_x, double fill_val_y )
{ {
if ( is_vector )
{
vals[2 * i] = MDAL::safeValue( vals_x[idx], fill_val_x ); vals[2 * i] = MDAL::safeValue( vals_x[idx], fill_val_x );
vals[2 * i + 1] = MDAL::safeValue( vals_y[idx], fill_val_y ); vals[2 * i + 1] = MDAL::safeValue( vals_y[idx], fill_val_y );
} }
else
static void populate_polar_vector_vals( double *vals, size_t i,
const std::vector<double> &vals_x, const std::vector<double> &vals_y,
size_t idx, double fill_val_x, double fill_val_y, std::pair<double, double> referenceAngles )
{
double magnitude = MDAL::safeValue( vals_x[idx], fill_val_x );
double direction = MDAL::safeValue( vals_y[idx], fill_val_y );
direction = 2 * M_PI * ( ( direction - referenceAngles.second ) / referenceAngles.first );
vals[2 * i] = magnitude * cos( direction );
vals[2 * i + 1] = magnitude * sin( direction );
}
static void populate_scalar_vals( double *vals, size_t i,
const std::vector<double> &rawVals,
size_t idx,
double fill_val )
{
vals[i] = MDAL::safeValue( rawVals[idx], fill_val );
}
static void fromClassificationToValue( const MDAL::Classification &classification, std::vector<double> &values, size_t classStartAt = 0 )
{
for ( size_t i = 0; i < values.size(); ++i )
{ {
vals[i] = MDAL::safeValue( vals_x[idx], fill_val_x ); if ( std::isnan( values[i] ) )
continue;
size_t boundIndex = size_t( values[i] ) - classStartAt;
if ( boundIndex >= classification.size() )
{
values[i] = std::numeric_limits<double>::quiet_NaN();
continue;
}
std::pair<double, double> bounds = classification.at( boundIndex );
double bound1 = bounds.first;
double bound2 = bounds.second;
if ( bound1 == NC_FILL_DOUBLE )
bound1 = bound2;
if ( bound2 == NC_FILL_DOUBLE )
bound2 = bound1;
if ( bound1 == NC_FILL_DOUBLE || bound2 == NC_FILL_DOUBLE )
values[i] = std::numeric_limits<double>::quiet_NaN();
else
values[i] = ( bound1 + bound2 ) / 2;
} }
} }
@ -175,6 +322,8 @@ void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vector<Relat
dsi.name dsi.name
); );
group->setIsScalar( !dsi.is_vector ); group->setIsScalar( !dsi.is_vector );
group->setIsPolar( dsi.is_polar );
group->setMetadata( dsi.metadata );
if ( dsi.outputType == CFDimensions::Vertex ) if ( dsi.outputType == CFDimensions::Vertex )
group->setDataLocation( MDAL_DataLocation::DataOnVertices ); group->setDataLocation( MDAL_DataLocation::DataOnVertices );
@ -275,6 +424,8 @@ std::shared_ptr<MDAL::Dataset> MDAL::DriverCF::create2DDataset( std::shared_ptr<
fill_val_y, fill_val_y,
dsi.ncid_x, dsi.ncid_x,
dsi.ncid_y, dsi.ncid_y,
dsi.classification_x,
dsi.classification_y,
dsi.timeLocation, dsi.timeLocation,
dsi.nTimesteps, dsi.nTimesteps,
dsi.nValues, dsi.nValues,
@ -491,14 +642,24 @@ bool MDAL::CFDimensions::isDatasetType( MDAL::CFDimensions::Type type ) const
////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////
MDAL::CFDataset2D::CFDataset2D( MDAL::DatasetGroup *parent, MDAL::CFDataset2D::CFDataset2D( MDAL::DatasetGroup *parent,
double fill_val_x, double fill_val_y, double fill_val_x,
int ncid_x, int ncid_y, CFDatasetGroupInfo::TimeLocation timeLocation, double fill_val_y,
size_t timesteps, size_t values, size_t ts, std::shared_ptr<NetCDFFile> ncFile ) int ncid_x,
int ncid_y,
Classification classification_x,
Classification classification_y,
CFDatasetGroupInfo::TimeLocation timeLocation,
size_t timesteps,
size_t values,
size_t ts,
std::shared_ptr<NetCDFFile> ncFile )
: Dataset2D( parent ) : Dataset2D( parent )
, mFillValX( fill_val_x ) , mFillValX( fill_val_x )
, mFillValY( fill_val_y ) , mFillValY( fill_val_y )
, mNcidX( ncid_x ) , mNcidX( ncid_x )
, mNcidY( ncid_y ) , mNcidY( ncid_y )
, mClassificationX( classification_x )
, mClassificationY( classification_y )
, mTimeLocation( timeLocation ) , mTimeLocation( timeLocation )
, mTimesteps( timesteps ) , mTimesteps( timesteps )
, mValues( values ) , mValues( values )
@ -547,14 +708,11 @@ size_t MDAL::CFDataset2D::scalarData( size_t indexStart, size_t count, double *b
for ( size_t i = 0; i < copyValues; ++i ) for ( size_t i = 0; i < copyValues; ++i )
{ {
populate_vals( false, populate_scalar_vals( buffer,
buffer,
i, i,
values_x, values_x,
std::vector<double>(),
i, i,
mFillValX, mFillValX );
mFillValY );
} }
return copyValues; return copyValues;
} }
@ -611,10 +769,30 @@ size_t MDAL::CFDataset2D::vectorData( size_t indexStart, size_t count, double *b
); );
} }
//if values component are classified convert from index to value
if ( !mClassificationX.empty() )
{
fromClassificationToValue( mClassificationX, values_x, 1 );
}
if ( !mClassificationY.empty() )
{
fromClassificationToValue( mClassificationY, values_y, 1 );
}
for ( size_t i = 0; i < copyValues; ++i ) for ( size_t i = 0; i < copyValues; ++i )
{ {
populate_vals( true, if ( group()->isPolar() )
buffer, populate_polar_vector_vals( buffer,
i,
values_x,
values_y,
i,
mFillValX,
mFillValY,
group()->referenceAngles() );
else
populate_vector_vals( buffer,
i, i,
values_x, values_x,
values_y, values_y,

View File

@ -61,11 +61,15 @@ namespace MDAL
std::string name; //!< Dataset group name std::string name; //!< Dataset group name
CFDimensions::Type outputType; CFDimensions::Type outputType;
bool is_vector; bool is_vector;
bool is_polar;
TimeLocation timeLocation; TimeLocation timeLocation;
size_t nTimesteps; size_t nTimesteps;
size_t nValues; size_t nValues;
int ncid_x; //!< NetCDF variable id int ncid_x; //!< NetCDF variable id
int ncid_y; //!< NetCDF variable id int ncid_y; //!< NetCDF variable id
Metadata metadata;
Classification classification_x;
Classification classification_y;
}; };
typedef std::map<std::string, CFDatasetGroupInfo> cfdataset_info_map; // name -> DatasetInfo typedef std::map<std::string, CFDatasetGroupInfo> cfdataset_info_map; // name -> DatasetInfo
@ -77,6 +81,8 @@ namespace MDAL
double fill_val_y, double fill_val_y,
int ncid_x, int ncid_x,
int ncid_y, int ncid_y,
Classification classification_x,
Classification classification_y,
CFDatasetGroupInfo::TimeLocation timeLocation, CFDatasetGroupInfo::TimeLocation timeLocation,
size_t timesteps, size_t timesteps,
size_t values, size_t values,
@ -93,6 +99,8 @@ namespace MDAL
double mFillValY; double mFillValY;
int mNcidX; //!< NetCDF variable id int mNcidX; //!< NetCDF variable id
int mNcidY; //!< NetCDF variable id int mNcidY; //!< NetCDF variable id
Classification mClassificationX; //!< Classification, void if not classified
Classification mClassificationY; //!< Classification, void if not classified
CFDatasetGroupInfo::TimeLocation mTimeLocation; CFDatasetGroupInfo::TimeLocation mTimeLocation;
size_t mTimesteps; size_t mTimesteps;
size_t mValues; size_t mValues;
@ -120,8 +128,13 @@ namespace MDAL
virtual void addBedElevation( MDAL::MemoryMesh *mesh ) = 0; virtual void addBedElevation( MDAL::MemoryMesh *mesh ) = 0;
virtual std::string getCoordinateSystemVariableName() = 0; virtual std::string getCoordinateSystemVariableName() = 0;
virtual std::set<std::string> ignoreNetCDFVariables() = 0; virtual std::set<std::string> ignoreNetCDFVariables() = 0;
virtual void parseNetCDFVariableMetadata( int varid, const std::string &variableName, virtual void parseNetCDFVariableMetadata( int varid,
std::string &name, bool *is_vector, bool *is_x ) = 0; std::string &variableName,
std::string &name,
bool *is_vector,
bool *isPolar,
bool *is_x ) = 0;
virtual std::vector<std::pair<double, double> > parseClassification( int varid ) const = 0;
virtual std::string getTimeVariableName() const = 0; virtual std::string getTimeVariableName() const = 0;
virtual std::shared_ptr<MDAL::Dataset> create2DDataset( virtual std::shared_ptr<MDAL::Dataset> create2DDataset(
std::shared_ptr<MDAL::DatasetGroup> group, std::shared_ptr<MDAL::DatasetGroup> group,

View File

@ -61,17 +61,17 @@ std::unique_ptr<MDAL::Mesh> MDAL::DriverEsriTin::load( const std::string &uri, c
inMsx.seekg( -4, std::ios::end ); inMsx.seekg( -4, std::ios::end );
int32_t mskBegin; int32_t mskBegin;
if ( ! readValue( mskBegin, inMsx, true ) ) if ( ! readValue( mskBegin, inMsx, true ) )
throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to find the beginning of data in msk file" ); throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to find the beginning of data in tmsx.adf file" );
//read information in mskFile //read information in mskFile
inMsk.seekg( -mskBegin * 2, std::ios::end ); inMsk.seekg( -mskBegin * 2, std::ios::end );
int32_t maskIntergerCount; int32_t maskIntergerCount;
if ( ! readValue( maskIntergerCount, inMsk, true ) ) if ( ! readValue( maskIntergerCount, inMsk, true ) )
throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in msk file" ); throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in tmsk.adf file" );
inMsk.ignore( 4 ); //unused 4 bytes inMsk.ignore( 4 ); //unused 4 bytes
int32_t maskBitsCount; int32_t maskBitsCount;
if ( ! readValue( maskBitsCount, inMsk, true ) ) if ( ! readValue( maskBitsCount, inMsk, true ) )
throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in msk file" ); throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in tmsk.adf file" );
int c = 0; int c = 0;
int32_t maskInt = 0; int32_t maskInt = 0;
@ -80,7 +80,7 @@ std::unique_ptr<MDAL::Mesh> MDAL::DriverEsriTin::load( const std::string &uri, c
//read mask file //read mask file
if ( c % 32 == 0 && c < maskBitsCount ) //first bit in the mask array have to be used-->read next maskInt if ( c % 32 == 0 && c < maskBitsCount ) //first bit in the mask array have to be used-->read next maskInt
if ( ! readValue( maskInt, inMsk, true ) ) if ( ! readValue( maskInt, inMsk, true ) )
throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in msk file" ); throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in tmsk.adf file" );
Face f; Face f;
for ( int i = 0; i < 3; ++i ) for ( int i = 0; i < 3; ++i )
@ -96,7 +96,7 @@ std::unique_ptr<MDAL::Mesh> MDAL::DriverEsriTin::load( const std::string &uri, c
break; break;
if ( f.size() < 3 ) //that's mean the face is not complete if ( f.size() < 3 ) //that's mean the face is not complete
throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in mask file, face is not complete" ); throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to read information in tnod.adf file, face is not complete" );
//exclude masked face //exclude masked face
if ( !( maskInt & 0x01 ) ) if ( !( maskInt & 0x01 ) )

View File

@ -54,6 +54,8 @@ MDAL::TuflowFVDataset2D::TuflowFVDataset2D(
double fillValY, double fillValY,
int ncidX, int ncidX,
int ncidY, int ncidY,
Classification classificationX,
Classification classificationY,
int ncidActive, int ncidActive,
CFDatasetGroupInfo::TimeLocation timeLocation, CFDatasetGroupInfo::TimeLocation timeLocation,
size_t timesteps, size_t timesteps,
@ -67,6 +69,8 @@ MDAL::TuflowFVDataset2D::TuflowFVDataset2D(
fillValY, fillValY,
ncidX, ncidX,
ncidY, ncidY,
classificationX,
classificationY,
timeLocation, timeLocation,
timesteps, timesteps,
values, values,
@ -456,10 +460,16 @@ std::set<std::string> MDAL::DriverTuflowFV::ignoreNetCDFVariables()
return ignore_variables; return ignore_variables;
} }
void MDAL::DriverTuflowFV::parseNetCDFVariableMetadata( int varid, const std::string &variableName, std::string &name, bool *is_vector, bool *is_x ) void MDAL::DriverTuflowFV::parseNetCDFVariableMetadata( int varid,
std::string &variableName,
std::string &name,
bool *is_vector,
bool *isPolar,
bool *is_x )
{ {
*is_vector = false; *is_vector = false;
*is_x = true; *is_x = true;
*isPolar = false;
std::string long_name = mNcFile->getAttrStr( "long_name", varid ); std::string long_name = mNcFile->getAttrStr( "long_name", varid );
if ( long_name.empty() || ( long_name == "??????" ) ) if ( long_name.empty() || ( long_name == "??????" ) )
@ -480,6 +490,8 @@ void MDAL::DriverTuflowFV::parseNetCDFVariableMetadata( int varid, const std::st
if ( MDAL::startsWith( long_name, "time at minimum value of " ) ) if ( MDAL::startsWith( long_name, "time at minimum value of " ) )
long_name = MDAL::replace( long_name, "time at minimum value of ", "" ) + "/Time at Minimums"; long_name = MDAL::replace( long_name, "time at minimum value of ", "" ) + "/Time at Minimums";
variableName = long_name;
if ( MDAL::startsWith( long_name, "x_" ) ) if ( MDAL::startsWith( long_name, "x_" ) )
{ {
*is_vector = true; *is_vector = true;
@ -515,6 +527,8 @@ std::shared_ptr<MDAL::Dataset> MDAL::DriverTuflowFV::create2DDataset(
fill_val_y, fill_val_y,
dsi.ncid_x, dsi.ncid_x,
dsi.ncid_y, dsi.ncid_y,
dsi.classification_x,
dsi.classification_y,
mNcFile->arrId( "stat" ), mNcFile->arrId( "stat" ),
dsi.timeLocation, dsi.timeLocation,
dsi.nTimesteps, dsi.nTimesteps,
@ -551,3 +565,9 @@ std::shared_ptr<MDAL::Dataset> MDAL::DriverTuflowFV::create3DDataset( std::share
dataset->setStatistics( MDAL::calculateStatistics( dataset ) ); dataset->setStatistics( MDAL::calculateStatistics( dataset ) );
return std::move( dataset ); return std::move( dataset );
} }
std::vector<std::pair<double, double>> MDAL::DriverTuflowFV::parseClassification( int varid ) const
{
MDAL_UNUSED( varid );
return std::vector<std::pair<double, double>>();
}

View File

@ -40,6 +40,8 @@ namespace MDAL
double fillValY, double fillValY,
int ncidX, int ncidX,
int ncidY, int ncidY,
Classification classificationX,
Classification classificationY,
int ncidActive, int ncidActive,
CFDatasetGroupInfo::TimeLocation timeLocation, CFDatasetGroupInfo::TimeLocation timeLocation,
size_t timesteps, size_t timesteps,
@ -122,8 +124,13 @@ namespace MDAL
void addBedElevation( MemoryMesh *mesh ) override; void addBedElevation( MemoryMesh *mesh ) override;
std::string getCoordinateSystemVariableName() override; std::string getCoordinateSystemVariableName() override;
std::set<std::string> ignoreNetCDFVariables() override; std::set<std::string> ignoreNetCDFVariables() override;
void parseNetCDFVariableMetadata( int varid, const std::string &variableName, void parseNetCDFVariableMetadata( int varid,
std::string &name, bool *is_vector, bool *is_x ) override; std::string &variableName,
std::string &name,
bool *is_vector,
bool *isPolar,
bool *is_x ) override;
std::vector<std::pair<double, double>> parseClassification( int varid ) const override;
std::string getTimeVariableName() const override; std::string getTimeVariableName() const override;
std::shared_ptr<MDAL::Dataset> create2DDataset( std::shared_ptr<MDAL::Dataset> create2DDataset(
std::shared_ptr<MDAL::DatasetGroup> group, std::shared_ptr<MDAL::DatasetGroup> group,

View File

@ -491,10 +491,17 @@ void MDAL::DriverUgrid::ignore2DMeshVariables( const std::string &mesh, std::set
ignoreVariables.insert( mNcFile->getAttrStr( mesh, "edge_face_connectivity" ) ); ignoreVariables.insert( mNcFile->getAttrStr( mesh, "edge_face_connectivity" ) );
} }
void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, const std::string &variableName, std::string &name, bool *isVector, bool *isX ) void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid,
std::string &variableName,
std::string &name,
bool *isVector,
bool *isPolar,
bool *isX )
{ {
*isVector = false; *isVector = false;
*isX = true; *isX = true;
*isPolar = false;
std::string longName = mNcFile->getAttrStr( "long_name", varid ); std::string longName = mNcFile->getAttrStr( "long_name", varid );
if ( longName.empty() ) if ( longName.empty() )
@ -506,6 +513,7 @@ void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, const std::strin
} }
else else
{ {
variableName = standardName;
if ( MDAL::contains( standardName, "_x_" ) ) if ( MDAL::contains( standardName, "_x_" ) )
{ {
*isVector = true; *isVector = true;
@ -525,6 +533,7 @@ void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, const std::strin
} }
else else
{ {
variableName = longName;
if ( MDAL::contains( longName, ", x-component" ) || MDAL::contains( longName, "u component of " ) ) if ( MDAL::contains( longName, ", x-component" ) || MDAL::contains( longName, "u component of " ) )
{ {
*isVector = true; *isVector = true;
@ -538,6 +547,20 @@ void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, const std::strin
name = MDAL::replace( longName, ", y-component", "" ); name = MDAL::replace( longName, ", y-component", "" );
name = MDAL::replace( name, "v component of ", "" ); name = MDAL::replace( name, "v component of ", "" );
} }
else if ( MDAL::contains( longName, "velocity magnitude" ) )
{
*isVector = true;
*isPolar = true;
*isX = true;
name = MDAL::replace( longName, " magnitude", "" );
}
else if ( MDAL::contains( longName, "velocity direction" ) )
{
*isVector = true;
*isPolar = true;
*isX = false;
name = MDAL::replace( longName, " direction", "" );
}
else else
{ {
name = longName; name = longName;
@ -807,3 +830,40 @@ void MDAL::DriverUgrid::writeGlobals()
mNcFile->putAttrStr( NC_GLOBAL, "date_created", MDAL::getCurrentTimeStamp() ); mNcFile->putAttrStr( NC_GLOBAL, "date_created", MDAL::getCurrentTimeStamp() );
mNcFile->putAttrStr( NC_GLOBAL, "Conventions", "CF-1.6 UGRID-1.0" ); mNcFile->putAttrStr( NC_GLOBAL, "Conventions", "CF-1.6 UGRID-1.0" );
} }
std::vector<std::pair<double, double>> MDAL::DriverUgrid::parseClassification( int varid ) const
{
std::vector<std::pair<double, double>> classes;
std::string flagBoundVarName = mNcFile->getAttrStr( "flag_bounds", varid );
if ( !flagBoundVarName.empty() )
{
try
{
int boundsVarId = mNcFile->getVarId( flagBoundVarName );
std::vector<size_t> classDims;
std::vector<int> classDimIds;
mNcFile->getDimensions( flagBoundVarName, classDims, classDimIds );
std::vector<double> boundValues = mNcFile->readDoubleArr( boundsVarId, 0, 0, classDims[0], classDims[1] );
if ( classDims[1] != 2 || classDims[0] <= 0 )
throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Invalid classification dimension" );
std::pair<std::string, std::string> classificationMeta;
classificationMeta.first = "classification";
std::string classification;
for ( size_t i = 0; i < classDims[0]; ++i )
{
std::pair<double, double> classBound;
classBound.first = boundValues[i * 2];
classBound.second = boundValues[i * 2 + 1];
classes.push_back( classBound );
}
}
catch ( MDAL::Error &err )
{
MDAL::Log::warning( err.status, err.driver, "Error when parsing class bounds: " + err.mssg + ", classification ignored" );
}
}
return classes;
}

View File

@ -38,8 +38,12 @@ namespace MDAL
void addBedElevation( MemoryMesh *mesh ) override; void addBedElevation( MemoryMesh *mesh ) override;
std::string getCoordinateSystemVariableName() override; std::string getCoordinateSystemVariableName() override;
std::set<std::string> ignoreNetCDFVariables() override; std::set<std::string> ignoreNetCDFVariables() override;
void parseNetCDFVariableMetadata( int varid, const std::string &variableName, void parseNetCDFVariableMetadata( int varid,
std::string &name, bool *is_vector, bool *is_x ) override; std::string &variableName,
std::string &name,
bool *is_vector, bool *isPolar,
bool *is_x ) override;
std::vector<std::pair<double, double>> parseClassification( int varid ) const override;
std::string getTimeVariableName() const override; std::string getTimeVariableName() const override;
void parse2VariablesFromAttribute( const std::string &name, const std::string &attr_name, void parse2VariablesFromAttribute( const std::string &name, const std::string &attr_name,

View File

@ -105,7 +105,7 @@ std::unique_ptr<MDAL::Mesh> MDAL::DriverXmsTin::load( const std::string &meshFil
// Read triangles // Read triangles
if ( !std::getline( in, line ) ) if ( !std::getline( in, line ) )
{ {
MDAL::Log::error( MDAL_Status::Err_IncompatibleMesh, name(), meshFile + " does not contain valid triangle definitions" ); MDAL::Log::error( MDAL_Status::Err_IncompatibleMesh, name(), meshFile + " does not contain valid triangle definition" );
return nullptr; return nullptr;
} }
chunks = split( line, ' ' ); chunks = split( line, ' ' );
@ -127,7 +127,7 @@ std::unique_ptr<MDAL::Mesh> MDAL::DriverXmsTin::load( const std::string &meshFil
if ( chunks.size() != 3 ) if ( chunks.size() != 3 )
{ {
// should have 3 indexes // should have 3 indexes
MDAL::Log::error( MDAL_Status::Err_IncompatibleMesh, name(), meshFile + " does not contain valid triangle definitions" ); MDAL::Log::error( MDAL_Status::Err_IncompatibleMesh, name(), meshFile + " does not contain valid triangle defintion" );
return nullptr; return nullptr;
} }

View File

@ -21,7 +21,7 @@ static const char *EMPTY_STR = "";
const char *MDAL_Version() const char *MDAL_Version()
{ {
return "0.5.91"; return "0.5.92";
} }
MDAL_Status MDAL_LastStatus() MDAL_Status MDAL_LastStatus()

View File

@ -187,6 +187,12 @@ void MDAL::DatasetGroup::setMetadata( const std::string &key, const std::string
metadata.push_back( std::make_pair( key, val ) ); metadata.push_back( std::make_pair( key, val ) );
} }
void MDAL::DatasetGroup::setMetadata( const MDAL::Metadata &metadata )
{
for ( const auto &meta : metadata )
setMetadata( meta.first, meta.second );
}
std::string MDAL::DatasetGroup::name() std::string MDAL::DatasetGroup::name()
{ {
return getMetadata( "name" ); return getMetadata( "name" );
@ -254,6 +260,26 @@ void MDAL::DatasetGroup::stopEditing()
mInEditMode = false; mInEditMode = false;
} }
void MDAL::DatasetGroup::setReferenceAngles( const std::pair<double, double> &referenceAngle )
{
mReferenceAngles = referenceAngle;
}
bool MDAL::DatasetGroup::isPolar() const
{
return mIsPolar;
}
void MDAL::DatasetGroup::setIsPolar( bool isPolar )
{
mIsPolar = isPolar;
}
std::pair<double, double> MDAL::DatasetGroup::referenceAngles() const
{
return mReferenceAngles;
}
MDAL_DataLocation MDAL::DatasetGroup::dataLocation() const MDAL_DataLocation MDAL::DatasetGroup::dataLocation() const
{ {
return mDataLocation; return mDataLocation;

View File

@ -38,6 +38,7 @@ namespace MDAL
} Statistics; } Statistics;
typedef std::vector< std::pair< std::string, std::string > > Metadata; typedef std::vector< std::pair< std::string, std::string > > Metadata;
typedef std::vector<std::pair<double, double>> Classification;
class Dataset class Dataset
{ {
@ -150,6 +151,7 @@ namespace MDAL
std::string getMetadata( const std::string &key ); std::string getMetadata( const std::string &key );
void setMetadata( const std::string &key, const std::string &val ); void setMetadata( const std::string &key, const std::string &val );
void setMetadata( const Metadata &metadata );
std::string name(); std::string name();
void setName( const std::string &name ); void setName( const std::string &name );
@ -179,12 +181,20 @@ namespace MDAL
void startEditing(); void startEditing();
void stopEditing(); void stopEditing();
//! First value is the angle for full rotation and second value is the start angle
void setReferenceAngles( const std::pair<double, double> &referenceAngle );
std::pair<double, double> referenceAngles() const;
bool isPolar() const;
void setIsPolar( bool isPolar );
private: private:
bool mInEditMode = false; bool mInEditMode = false;
const std::string mDriverName; const std::string mDriverName;
Mesh *mParent = nullptr; Mesh *mParent = nullptr;
bool mIsScalar = true; bool mIsScalar = true;
bool mIsPolar = true;
std::pair<double, double> mReferenceAngles = {360, 0};
MDAL_DataLocation mDataLocation = MDAL_DataLocation::DataOnVertices; MDAL_DataLocation mDataLocation = MDAL_DataLocation::DataOnVertices;
std::string mUri; // file/uri from where it came std::string mUri; // file/uri from where it came
Statistics mStatistics; Statistics mStatistics;

View File

@ -23,6 +23,10 @@
#define MDAL_UNUSED(x) (void)x; #define MDAL_UNUSED(x) (void)x;
#define MDAL_NAN std::numeric_limits<double>::quiet_NaN() #define MDAL_NAN std::numeric_limits<double>::quiet_NaN()
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327
#endif
namespace MDAL namespace MDAL
{ {
// endianness // endianness

View File

@ -185,6 +185,11 @@ QString QgsMeshRendererActiveDatasetWidget::metadata( QgsMeshDatasetIndex datase
const auto options = gmeta.extraOptions(); const auto options = gmeta.extraOptions();
for ( auto it = options.constBegin(); it != options.constEnd(); ++it ) for ( auto it = options.constBegin(); it != options.constEnd(); ++it )
{ {
if ( it.key() == QStringLiteral( "classification" ) )
{
msg += QStringLiteral( "<tr><td>%1</td></tr>" ).arg( tr( "Classified values" ) );
continue;
}
msg += QStringLiteral( "<tr><td>%1</td><td>%2</td></tr>" ).arg( it.key() ).arg( it.value() ); msg += QStringLiteral( "<tr><td>%1</td><td>%2</td></tr>" ).arg( it.key() ).arg( it.value() );
} }

View File

@ -112,7 +112,6 @@ void QgsMeshRendererScalarSettingsWidget::syncToLayer( )
const double min = settings.classificationMinimum(); const double min = settings.classificationMinimum();
const double max = settings.classificationMaximum(); const double max = settings.classificationMaximum();
whileBlocking( mScalarMinLineEdit )->setText( QString::number( min ) ); whileBlocking( mScalarMinLineEdit )->setText( QString::number( min ) );
whileBlocking( mScalarMaxLineEdit )->setText( QString::number( max ) ); whileBlocking( mScalarMaxLineEdit )->setText( QString::number( max ) );
whileBlocking( mScalarColorRampShaderWidget )->setFromShader( shader ); whileBlocking( mScalarColorRampShaderWidget )->setFromShader( shader );

View File

@ -116,6 +116,11 @@ void QgsRendererMeshPropertiesWidget::apply()
if ( activeVectorDatasetGroupIndex > -1 ) if ( activeVectorDatasetGroupIndex > -1 )
settings.setVectorSettings( activeVectorDatasetGroupIndex, mMeshRendererVectorSettingsWidget->settings() ); settings.setVectorSettings( activeVectorDatasetGroupIndex, mMeshRendererVectorSettingsWidget->settings() );
QgsMeshDatasetIndex staticScalarDatasetIndex( activeScalarDatasetGroupIndex, mMeshLayer->staticScalarDatasetIndex().dataset() );
QgsMeshDatasetIndex staticVectorDatasetIndex( activeVectorDatasetGroupIndex, mMeshLayer->staticVectorDatasetIndex().dataset() );
mMeshLayer->setStaticScalarDatasetIndex( staticScalarDatasetIndex );
mMeshLayer->setStaticVectorDatasetIndex( staticVectorDatasetIndex );
//set the blend mode for the layer //set the blend mode for the layer
mMeshLayer->setBlendMode( mBlendModeComboBox->blendMode() ); mMeshLayer->setBlendMode( mBlendModeComboBox->blendMode() );
//set the averaging method for the layer //set the averaging method for the layer

View File

@ -108,6 +108,10 @@ void QgsMeshLayer::setDefaultRendererSettings()
case QgsMeshDatasetGroupMetadata::DataOnEdges: case QgsMeshDatasetGroupMetadata::DataOnEdges:
break; break;
} }
//override color ramp if the values in the dataset group are classified
applyClassificationOnScalarSettings( meta, scalarSettings );
mRendererSettings.setScalarSettings( i, scalarSettings ); mRendererSettings.setScalarSettings( i, scalarSettings );
} }
@ -407,6 +411,94 @@ QgsMeshDatasetIndex QgsMeshLayer::datasetIndexAtTime( const QgsDateTimeRange &ti
return QgsMeshDatasetIndex(); return QgsMeshDatasetIndex();
} }
void QgsMeshLayer::applyClassificationOnScalarSettings( const QgsMeshDatasetGroupMetadata &meta, QgsMeshRendererScalarSettings &scalarSettings ) const
{
if ( meta.extraOptions().contains( QStringLiteral( "classification" ) ) )
{
QgsColorRampShader colorRampShader = scalarSettings.colorRampShader();
QgsColorRamp *colorRamp = colorRampShader.sourceColorRamp();
QStringList classes = meta.extraOptions()[QStringLiteral( "classification" )].split( QStringLiteral( ";;" ) );
QString units;
if ( meta.extraOptions().contains( QStringLiteral( "units" ) ) )
units = meta.extraOptions()[ QStringLiteral( "units" )];
QVector<QVector<double>> bounds;
for ( const QString classe : classes )
{
QStringList boundsStr = classe.split( ',' );
QVector<double> bound;
for ( const QString boundStr : boundsStr )
bound.append( boundStr.toDouble() );
bounds.append( bound );
}
if ( ( bounds.count() == 1 && bounds.first().count() > 2 ) || // at least a class with two value
( bounds.count() > 1 ) ) // or at least two classes
{
const QVector<double> firstClass = bounds.first();
const QVector<double> lastClass = bounds.last();
double minValue = firstClass.count() > 1 ? ( firstClass.first() + firstClass.last() ) / 2 : firstClass.first();
double maxValue = lastClass.count() > 1 ? ( lastClass.first() + lastClass.last() ) / 2 : lastClass.first();
double diff = maxValue - minValue;
QList<QgsColorRampShader::ColorRampItem> colorRampItemlist;
for ( int i = 0; i < bounds.count(); ++i )
{
const QVector<double> &boundClass = bounds.at( i );
QgsColorRampShader::ColorRampItem item;
item.value = i + 1;
if ( !boundClass.isEmpty() )
{
double scalarValue = ( boundClass.first() + boundClass.last() ) / 2;
item.color = colorRamp->color( ( scalarValue - minValue ) / diff );
if ( i != 0 && i < bounds.count() - 1 ) //The first and last labels are treated after
{
item.label = QString( ( "%1 - %2 %3" ) ).
arg( QString::number( boundClass.first() ) ).
arg( QString::number( boundClass.last() ) ).
arg( units );
}
}
colorRampItemlist.append( item );
}
//treat first and last labels
if ( firstClass.count() == 1 )
colorRampItemlist.first().label = QObject::tr( "below %1 %2" ).
arg( QString::number( firstClass.first() ) ).
arg( units );
else
{
colorRampItemlist.first().label = QString( ( "%1 - %2 %3" ) ).
arg( QString::number( firstClass.first() ) ).
arg( QString::number( firstClass.last() ) ).
arg( units );
}
if ( lastClass.count() == 1 )
colorRampItemlist.last().label = QObject::tr( "above %1 %2" ).
arg( QString::number( lastClass.first() ) ).
arg( units );
else
{
colorRampItemlist.last().label = QString( ( "%1 - %2 %3" ) ).
arg( QString::number( lastClass.first() ) ).
arg( QString::number( lastClass.last() ) ).
arg( units );
}
colorRampShader.setMinimumValue( 0 );
colorRampShader.setMaximumValue( colorRampItemlist.count() - 1 );
scalarSettings.setClassificationMinimumMaximum( 0, colorRampItemlist.count() - 1 );
colorRampShader.setColorRampItemList( colorRampItemlist );
colorRampShader.setColorRampType( QgsColorRampShader::Exact );
colorRampShader.setClassificationMode( QgsColorRampShader::EqualInterval );
}
scalarSettings.setColorRampShader( colorRampShader );
scalarSettings.setDataResamplingMethod( QgsMeshRendererScalarSettings::None );
}
}
QgsMeshDatasetIndex QgsMeshLayer::activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const QgsMeshDatasetIndex QgsMeshLayer::activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const
{ {
if ( mTemporalProperties->isActive() ) if ( mTemporalProperties->isActive() )

View File

@ -461,6 +461,9 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer
QgsMeshDatasetIndex datasetIndexAtTime( const QgsDateTimeRange &timeRange, int datasetGroupIndex ) const; QgsMeshDatasetIndex datasetIndexAtTime( const QgsDateTimeRange &timeRange, int datasetGroupIndex ) const;
//! Changes scalar settings for classified scalar value (information about is in the metadata
void applyClassificationOnScalarSettings( const QgsMeshDatasetGroupMetadata &meta, QgsMeshRendererScalarSettings &scalarSettings ) const;
private slots: private slots:
void onDatasetGroupsAdded( int count ); void onDatasetGroupsAdded( int count );

View File

@ -587,5 +587,4 @@ QVector<QVector3D> QgsMeshLayerUtils::calculateNormals( const QgsTriangularMesh
return normals; return normals;
} }
///@endcond ///@endcond

View File

@ -94,6 +94,7 @@ class TestQgsMeshRenderer : public QObject
void test_vertex_vector_traces_colorRamp(); void test_vertex_vector_traces_colorRamp();
void test_stacked_3d_mesh_single_level_averaging(); void test_stacked_3d_mesh_single_level_averaging();
void test_simplified_triangular_mesh_rendering(); void test_simplified_triangular_mesh_rendering();
void test_classified_values();
void test_signals(); void test_signals();
}; };
@ -681,7 +682,19 @@ void TestQgsMeshRenderer::test_simplified_triangular_mesh_rendering()
QVERIFY( imageCheck( "simplified_triangular_mesh", mMdal3DLayer ) ); QVERIFY( imageCheck( "simplified_triangular_mesh", mMdal3DLayer ) );
} }
// TODO test edge mesh rendering! void TestQgsMeshRenderer::test_classified_values()
{
QgsMeshLayer classifiedMesh( mDataDir + "/simplebox_clm.nc", "Mesh with classified values", "mdal" );
QVERIFY( classifiedMesh.isValid() );
QgsProject::instance()->addMapLayer( &classifiedMesh );
mMapSettings->setLayers( QList<QgsMapLayer *>() << &classifiedMesh );
classifiedMesh.temporalProperties()->setIsActive( false );
classifiedMesh.setStaticScalarDatasetIndex( QgsMeshDatasetIndex( 3, 4 ) );
QVERIFY( imageCheck( "classified_values", &classifiedMesh ) );
}
QGSTEST_MAIN( TestQgsMeshRenderer ) QGSTEST_MAIN( TestQgsMeshRenderer )
#include "testqgsmeshlayerrenderer.moc" #include "testqgsmeshlayerrenderer.moc"

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
tests/testdata/mesh/simplebox_clm.nc vendored Normal file

Binary file not shown.