W
MediStef schrieb:
Danke Werner für die Rückmeldung. Wie hast du die Datei ausgelesen? Hast du mein Prog übernommen oder es mit nem anderen Reader gemacht?
Hallo Stefan,
ich habe einfach ignore-bis-char(3) gemacht und dann die verbleibenden Bytes gezählt. Dazu waren nur eine Handvoll Programmzeilen notwendig.
MediStef schrieb:
Wenn das tatsächlich keine 900x900 sind, sondern mehr, muss ich wohl mal ein Wörtchen mit dem DWD sprechen. Oder hab ich was Wichtiges in der PDF überlesen
Vielleicht - es gibt noch zwei Informationen über die Anzahl die folgenden Datensätze.
Zum einen steht hinter der Kennung 'GP' die Anzahl der Pixel - hier '900x900' und hinter der Kennung 'BY' steht noch mal die Größe des gesamten Datensatzes inklusive des Headers - hier '1620245' bei einer Header-Länge von 245 sind das genau 1620000 Byte macht 1620000/2=810000=900x900 Datensätze.
Die beiden Angaben sind redundant, da die Datensatzlänge von 2Byte für das 'SF'-Format festgelegt ist; im Grunde ist das schlechter Stil - na ja - wahrscheinlich ein 'gewachsenes' Format.
Ich würde Dir zunächst mal empfehlen eine struct für so einen Datensatz zu bauen:
#include <cstdint> // std::uint16_t
#include <cassert>
#include <istream>
namespace RADOLAN
{
struct Datensatz
{
Datensatz()
: data_(0x29C4) // invalid
{}
bool is_cluttermarkierung() const
{
return (data_ & 0x8000) != 0;
}
bool valid() const
{
return (data_ & 0x2000) == 0;
}
int get() const
{
assert( valid() );
int ret = data_ & 0x0fff;
if( data_ & 0x4000 ) // Vorzeichen-Bit
ret = -ret;
return ret;
}
friend std::istream& operator>>( std::istream& in, Datensatz& d );
private:
std::uint16_t data_; // ist sicher 2 Byte groß!
};
}
Der Streaming-Operator in Zeile 28 liest so einen Datensatz binär ein - Implementierung:
namespace RADOLAN
{
// -- Einlesen im Binärformat aus 2 byte (little endian)
std::istream& operator>>( std::istream& in, Datensatz& d )
{
std::istream::sentry ok( in, true ); // true := noskip von 'White Space Charaters'
if( ok )
{
std::ios_base::iostate state = std::ios_base::goodbit;
try
{
char buf[2];
if( in.rdbuf()->sgetn( buf, 2 ) == 2 )
{ // little endian -> LOW Byte; HIGH Byte
d.data_ = static_cast< std::uint16_t >( static_cast< unsigned char >( buf[0] ) )
| (static_cast< std::uint16_t >( static_cast< unsigned char >( buf[1] ) ) << 8);
}
else
state |= std::ios_base::eofbit | std::ios_base::failbit;
}
catch( ... )
{
state |= std::ios_base::badbit;
if( in.exceptions() & std::ios_base::badbit )
throw;
}
in.setstate( state );
}
return in;
}
}
Ich habe diesen Extractor wie auch die folgenden gleich auf 'streambuf-Ebene' realisiert - näheres dazu findest Du hier: http://www.c-plusplus.net/forum/p2256837#2256837
Mit Hilfe des obigen Codes kannst Du so eine Datei sehr leicht einlesen:
#include <iostream>
#include <fstream>
#include <limits>
#include <vector>
int main()
{
using namespace std;
using namespace RADOLAN;
const char ETX(3);
ifstream readfile( "data.bin", ios_base::binary );
if( !readfile.is_open() )
{
cerr << "Fehler beim Oeffnen" << endl;
return -2;
}
readfile.ignore( numeric_limits< streamsize >::max(), ETX ); // Header überlesen
vector< Datensatz > pixel;
int count = 0;
for( Datensatz d; count < 900*900 && readfile >> d; ++count )
pixel.push_back( d );
if( readfile )
cout << "ohne Fehler gelesen" << endl;
}
MediStef schrieb:
Ich bleib bei meiner Variante, da ich den Header eventuell noch benötige.
.. dann solltest Du diesen auch lesen und nicht nur die Zeichen in einen String kopieren
Der Header besteht aus mehreren Einträgen, die immer mit einer Kennung beginnen. Für die Kennung habe ich auch ein Helferlein gebastelt, so kann man z.B. prüfen, ob das überhaupt ein 'SF'-Format ist
// -- einfügen vor Zeile 18
Kennung kennung;
if( (readfile >> kennung).fail() || kennung != "SF" )
{
cerr << "kein RADOLAN-SF-Dateiformat" << endl;
return -3;
}
Die Implementierung dazu:
#include <string>
#include <istream>
namespace RADOLAN
{
struct Kennung
{
Kennung() : str_() {}
bool operator==( const std::string& b ) const
{
return str_ == b;
}
bool operator!=( const std::string& b ) const
{
return str_ != b;
}
friend std::istream& operator>>( std::istream& in, Kennung& tok );
private:
std::string str_;
};
std::istream& operator>>( std::istream& in, Kennung& tok )
{
std::istream::sentry ok( in, true );
if( ok )
{
std::ios_base::iostate state = std::ios_base::goodbit;
try
{
typedef std::istream::traits_type Traits;
const char ETX(3);
std::string str;
for( int i=0; i<2; ++i )
{
Traits::int_type m = in.rdbuf()->sbumpc();
if( Traits::eq_int_type( m, Traits::eof() ) )
{
state |= std::ios_base::eofbit | std::ios_base::failbit;
break;
}
const char c = Traits::to_char_type(m);
str.append( 1, c );
if( c == ETX )
break;
}
if( state == std::ios_base::goodbit )
swap( str, tok.str_ );
}
catch( ... )
{
state |= std::ios_base::badbit;
if( in.exceptions() & std::ios_base::badbit )
throw;
}
in.setstate( state );
}
return in;
}
}
das allein macht noch keinen Sinn, ohne auch die Parameter hinter den Kennungen zu lesen. Der std::istream ist per se nicht in der Lage Zahlen mit einer festen Anzahl von Ziffern einzulesen und dann das Lesen zu beenden. Im Allgemeinen muss ein Datum (Zahl oder String) immer mit einen White-Space-Character beendet werden, damit es gelesen werden kann. Das ist bei diesem FORTRAN-Format nicht der Fall.
Deshalb ist ein weiteres Helferlein fällig, welches Integer mit fester Stellen liest. Damit kann dann z.B. hinter "SF" das Datum gelesen werden:
int tag, monat, jahr;
if( (readfile >> number<2>( tag )).ignore( 2*2+5 ) >> number<2>( monat ) >> number<2>( jahr ) )
{ // Format: 3I2 I5 2I2
cout << "Datum: " << tag << "." << monat << ".20" << jahr << endl;
}
Die Implementierung von number<> :
namespace RADOLAN
{
// -- der 'number_reader' liest eine Folge von genau 'N' Ziffern in eine Variable
// vom Typ 'T' ein. (Dezimalsystem)
template< int N, typename T >
struct number_reader
{
number_reader( T& number )
: number_( number ) {}
friend std::istream& operator>>( std::istream& in, number_reader nr )
{
std::istream::sentry ok( in, true ); // true := noskip
if( ok )
{
T number = 0;
std::ios_base::iostate state = std::ios_base::goodbit;
try
{
typedef std::istream::traits_type Traits;
bool digit_read = false;
const std::ctype< char >& ct = std::use_facet< std::ctype< char > >( in.getloc() );
for( int i=0; i<N; ++i )
{
const Traits::int_type m = in.rdbuf()->sbumpc();
if( Traits::eq_int_type( m, Traits::eof() ) )
{
state |= std::ios_base::eofbit | std::ios_base::failbit;
break;
}
const char c = Traits::to_char_type( m );
if( ct.is( std::ctype_base::digit, c ) || (!digit_read && ct.is( std::ctype_base::space, c )) )
{
T digit = 0;
if( ct.is( std::ctype_base::digit, c ) )
{
digit_read = true;
digit = T(c - '0');
}
if( number > (std::numeric_limits< T >::max() - digit)/10 )
{ // overflow
state |= std::ios_base::failbit;
break;
}
(number *= 10) += digit;
}
else
{
state |= std::ios_base::failbit;
break;
}
}
if( (state & std::ios_base::failbit) == 0 )
nr.number_ = number;
}
catch( ... )
{
state |= std::ios_base::badbit;
if( in.exceptions() & std::ios_base::badbit )
throw;
}
in.setstate( state );
}
return in;
}
private:
T& number_;
};
template< int N, typename T >
number_reader< N, T > number( T& n )
{
return number_reader< N, T >( n );
}
}
Damit ist dann auch der nächste Datensatz 'BY' kein Problem mehr:
size_t size;
readfile >> kennung;
if( kennung != "BY" )
readfile.setstate( ios_base::failbit );
readfile >> number<7>( size ); // Format I7
.. die weiteren überlasse ich Dir.
Gruß
Werner