import java.awt.Point;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;


/**
 * RadolanReader
 * Liest eine RADOLAN-Datei und stellt das gelesene Feld als
 * Integer- oder Float-Feld bereit.
 * 
 * Zur Anwendung dieser Klasse inkl. der Test-Klasse gibt es
 * eine weitere Dokumentation als PDF-Datei.
 * 
 * @author
 * -------
 * Deutscher Wetterdienst - Hydrometeorologie - Mario Hafer, KU42a, -2985
 * Version: s. die Zuweisung im Programmcode => VERSION
 * 
 * Anpassungen (Historie mit der Testklasse 'RadolanReaderTest' abgleichen)
 * -----------
 * 2019-03-07: Lesen des RADVOR-RE-Formats eingebaut (Anfrage LfU)
 *             da größere Umbauarbeiten an den Methoden und Attributen -
 *             mit Wegfall der Klasse 'Pixel' und 'Header' großer Versionssprung
 *             -> Version 2.0
 * 2017-03-28: RW-Tests und RQ-Kompatibiltätstest (LfU möchte damit auch RADVOR-RQ verarbeiten)
 *             -> Version 1.0
 * =============================================================== */
public class RadolanReader {
	
    //final String VERSION = "1.0";
    public static final String VERSION = "2.0";   // 2019/03
    
	/*
	 * Die Beschreibung der Flags ist noch nicht vollständig.
	 * 
	 * - Bit 13: Markierung der Bodendaten im RW.
	 *   Korrektur: wert = wert - 2^12 (4096)
	 * - Bit 14: Fehlkennung (nicht belegte Bereiche)
	 * - Bit 15: bisher noch nicht behandelt (erkannt) -> Negatives Vorzeichen
	 * - Bit 16 = Clutterkennung
	 *   
	 * - Fehlkennung:
	 *   Fehlkennung (10692) tauchte im Komposit schon auf, normalerweise ganz alleine
	 *   an den RADOLAN-Raendern (10692), trat aber auch schon in Kombination mit Bit 16
	 *   (+ 32768 = 43460) auf.
	 * - Clutterkennung:
	 *   Damit könnte auch Bit 16 + Fehlkennung (43460) gemeint sein,
	 *   dies wurde schon im RA-Produkt (Differenzen-Aneichung) gefunden.
	 */

	final int BIT13       =  4096;
	final int BIT14       =  8192;
	final int BIT16       = 32768;
	final int CLUTTER     = BIT16 + 2490;    // historisch mit dem +Wert?
	final int MISSING_RAW = BIT14 + 2500;    // = 10692
	final int MISSING_NEG = -1;
	
	// Zumeist angeeichte Produkte (Bodenflag, Bit13)
	List<String> products_using_bit13 =
	    Arrays.asList("RW", "EW", "RL", "RU", "SF", "W1", "W2", "W3", "W4", "RE");
	
	// --- Ende Bits ---
	
	/*
	 * Variablen mit Bezug zum RADOLAN-File
	 */
	private File file;
	// "Record-/Bytelänge": Wird ggf. in Spezialklasse überschrieben (RX).
	protected int BYTES = 2;
	protected DataInputStream radolan;
	
	
	/*
	 * Ermittelt
	 */
	
	// aus RADOLAN-ASCII-Header:
	private String prod_id;    // Produktkennung (z.B. RW, RX...)
    // Genauigkeit der RADOLAN-Werte. Nur kleine Werte, 'float' sollte reichen:
	private float factor = 1.0F;
    /* Zeilen- und Spaltenanzahl des Kompositbildes
     * (werden 1 x beim Anlegen des Objekts gesetzt): */
    private int MAX_ROWS, MAX_COLS;
    private String header;    // String-Header
    
	// Gesamtes RADOLAN-Feld:
	private int[][]   int_field;
	private float[][] float_field;
	
	
	/*
	 * Flags zur Steuerung des Programverhaltens:
	 */

	// Standardmäßig Flags berücksichtigen, d.h. vom Integer-Wert korrigieren:
	boolean consider_flags = true;
	// Soll der Dezimalwert für Bit13 abgezogen werden?
	// Wird durch den Header bestimmt. Für das RADOLAN-RW/EW und RADVOR-RE ist das der Fall.
	boolean consider_bit_13 = false;
	/* Aus Binär-zu-Integer umgerechnete Werte so belassen, wie sie sind.
	 * Auch keine Fehlwerte zu minus 1 Korrektur.
	 * Hilfreich für Debugging (was steht in einem unbekannten Produkt?). */
	private boolean raw_values = false;
	
	// Flag für Vermerken, ob Feld schon Nord-Süd-Orientierung hat (dann nicht nochmal drehen):
	boolean is_north = false;
	
	// Wird für Fehler-Rückmeldung aufrufender Programme verwendet:
	private String error;
	
	
	

	/*
	 * -------------------- Nur Ausgabemethoden ----------------------
	 */
    
	private void out(String msg) {
		out(msg, true);
	}
	private void out(String msg, boolean ok) {
		String printout = getClass().getName() + ": " + msg;
		if(ok)
			System.out.println(printout);
		else
			System.err.println(printout);
	}
	
	
	
	/*
	 * -------------------- Konstruktoren ----------------------
	 */
	
	
	/** Konstruktor für File-Objekt-Übergabe 
	 * @throws IOException */
	public RadolanReader(String file) throws IOException {
		this( new File(file) );
	}

	/**
	 * Konstruktor
	 * 
	 * Öffnet einen InputStream mit der damit verbundenen RADOLAN-Datei,
	 * sodass danach alle weiteren Methoden auf die Datei angewendet werden
	 * können.
	 * Mit RandomAccessFile oder BufferedReader sind Bytes schlecht lesbar.
	 * 
	 * Header wird gleich gelesen, da dies eigentlich immer getan werden muss
	 * um
	 * 1) an die Header-Informationen zu kommen
	 * 2) nachfolgend den Binärteil zu lesen
	 * @throws IOException 
	 * @throws FileNotFoundException
	 */
	public RadolanReader(File file) throws IOException {

	    // Datei für weitere Verwendungen setzen:
	    this.file = file;

	    out("Version " + VERSION);
	    System.out.println();

	    out("<- " + file);

	    /* Checken, ob übergebene Datei eine Größe (Inhalt) hat.
	     * Das kommt zwar kaum vor, aber... */
	    if(file.length() == 0)
	        throw new IOException("! Datei \"" + file + "\" ist leer.");



	    FileInputStream fis = new FileInputStream(file);

	    /* To create DataInputStream object,
	     * use DataInputStream(InputStream in) constructor. */
	    radolan = new DataInputStream(fis);

	    //out("RADOLAN-File '" + file + "' geöffnet.");
	    
	    
	    // 1) Dateizeiger an den Beginn des Binärteils bewegen
	    // 2) Nötige Informationen über das Produkt aus dem Header gewinnen
        read_header();
        
        // Metadaten sind da, Informationen zum Produkt ausgeben:
        print_info();

	} // RadolanReader()
	
	
    /**
     * Lesen des Headers der Produktdatei
     * 
     * Zweck:
     * Dies ist das erste Lesen der Datei.
     * Der Header muss immer gelesen werden, damit der Dateizeiger
     * an den Anfang des Binaerteils positioniert wird.
     * Ausserdem wird auf Grundlage des Headers einige wichtige
     * Produkteigenschaften gesetzt.
     * Die Idee ist naemlich, viele Uebergaben an den Konstruktor
     * (die fehlerhaft gewaehlt sein koennten) zu vermeiden, was
     * das Programm uebersichtlicher und einfacher gestaltet,
     * indem Informationen, die fuer den Lauf wichtig sind, aus
     * dem Header zu beziehen (sozusagen sich die Klasse
     * selbst mit den notwendigen Daten "versorgen" kann).
     * 
     * Aus diesen Gruenden muss der Header (diese Methode) immer
     * gelesen (ausgefuehrt) werden.
     * Daher sollten auch nicht zuviele Extrahier-Operationen in diese
     * Methode gepackt werden, da diese bei Batch-Auswertungen immer
     * durchlaufen wird und diese die somit unnoetig verlangsamen koennte,
     * da Dinge extrahiert werden, die evtl. fuer die meisten
     * Anwendungsfaelle gar nicht benoetigt werden!
     * Moechte man ausfuehrliche Header-Infos haben, sind dafuer
     * die speziellen Methoden die richtige Wahl; dort koennen auch die
     * erweiterten Informationen bereitgestellt werden.
     * Also sollten hier eigentlich nur Angaben ausgewertet werden,
     * die zum Lesen einer RADOLAN-Datei noetig sind.
     *
     * Ergaenzendes:
     * Das Vorhandensein des Header-Endezeichens ETX wird vorausgesetzt
     * (wird nicht weiter geprueft).
     * Es ist eine Zusatzpruefung enthalten, ob es sich bei der
     * einzulesenden Datei wirklich um eine RADOLAN-Datei handelt.
     * Ist das nicht der Fall, wird der weitere Lesevorgang abgebrochen.
     * @throws IOException 
     */
    private void read_header() throws IOException {
        
        out("read_header()");
        
        // Das "Header-Ende-Zeichen":
        final char ETX = (char)0x03;


        StringBuilder tmp = new StringBuilder(); // <- Inhalte werden hier dazugefügt

        int ch, i = 0; // Zeichen, Zeichenzähler

        final String standard_error_msg = "'" + file + "' ist keine RADOLAN-Datei!";


        // Zeichen(Byte)weise Lesen bis zur Ende-Markierung...
        while ( (ch = radolan.read()) != ETX ) {
            /* Da die read() einer Readerklasse jedes Zeichen als int
             * liefert, muss dieses int zur Ausgabe auf char gecastet werden. */
            tmp.append((char)ch);

            if(i == 1) { // <- 2 Zeichen gelesen
                prod_id = tmp.toString();
            }

            i++;

            // Mit dieser Zusatzbedingung wird eine evtl. Endlosschleife abgebrochen:
            if(i > 500) {
                String ex = "Header-Endezeichen 'ETX' nicht gefunden! " + standard_error_msg;
                throw new IOException(ex);
            }

        } // while
        
        
        if(products_using_bit13.contains(prod_id)){
            consider_bit_13 = true;
            out("consider_bit_13: " + consider_bit_13);
        }
        
        // StringBuilder in String umwandeln:
        header = tmp.toString();

        // Auf Grundlage des Headers einige wichtige Produkteigenschaften setzen:

        /* 1) Auflösung:
         * Durchsucht wird z.B. eine Stelle, die so aussehen kann
         * Beispiel "RW": ... E-01INT  60GP 900x 900MS ...
         * Beispiel "EW": ... E-01INT  60GP1500x1400MS122 ...
         *                               ^^^^^^^^^^^ */
        int index = header.indexOf("GP");
        // KEIN FUND (dürfte nicht vorkommen, wenn das Format korrekt ist):
        if(index < 0) {
            String ex = "Kennung \"GP\" nicht im Header gefunden!" + standard_error_msg;
            throw new IOException(ex);
        }

        // Dimension des Produkts d.h. Anzahl der Pixel setzen

        //System.out.println("-> Pixelangabe ab Zeichen " + index);
        // 2 Zeichen weiter beginnt die Angabe der Aufloesung:
        String sub = header.substring(index+=2, index+9);
        // Inhalt ware jetzt z.B.: " 900x 900" -> Leerzeichen! Entfernen:
        // Alle "Whitespaces" - 'trim()' reicht nicht:
        sub = sub.replaceAll("\\s", ""); // jetzt z.B.: "900x900"
        // Nun am Trennzeichen ('x') splitten:
        String[] xy = sub.split("x");

        // Groesserer Wert (bei Europa) muessten die Spalten sein:
        MAX_COLS = Integer.parseInt(xy[0]);
        MAX_ROWS = Integer.parseInt(xy[1]);

        /* 1) Genauigkeit:
         * Genauigkeitsangabe des Produkts in Bezug auf die codierten
         * Niederschlagswerte extrahieren und setzen:
         * 
        Suchen im Header nach dem String "E-01" oder "E-02"
        - gibt die Nachkomma-Genauigkeit an.

        Beispiel:
        RW280050100000408BY1620138VS 2SW    2.0.1PR E-01INT  60GP 900x 900MS
                                                    ^^^^
        RX:
        RX120650100000210BY 810138VS 2SW    2.3.0PR E+00INT   5GP 900x 900MS 66<ham,ros,emd,
                                                    ^^^^
         * 
         */

        // Genauigkeit in zehntel mm:
        if(header.indexOf("E-01") >= 0)
            factor = 0.1F;

        // Genauigkeit in hunderstel mm:
        else if(header.indexOf("E-02") >= 0)
            factor = 0.01F;

        // Genauigkeit in tausendstel mm: für RADVOR-RE-Produkt
        else if(header.indexOf("E-03") >= 0)
            factor = 0.001F;

        // Genauigkeit unbekannt oder RX, Wert unverändert lassen:
        else
            factor = 1F;

        //System.out.println("Faktor festgestellt: " + faktor);

        // Datei bleibt offen...

    } // read_header()
    
	
	/*
	 * Methoden zum Steuern des Programmverhaltens
	 */
	
	/** Methode zum Deaktivieren der Flag-Korrektur. */
	public void consider_flags(boolean b) {
		this.consider_flags = b;
		out("consider_flags: " + consider_flags);
	}
	
	/** Integerwerte, so wie sie aus Binär zu Int codiert wurden, belassen. */
	public void raw_values(boolean b) {
		this.raw_values = b;
		out("raw_values: " + raw_values);
		
		// Zur Sicherheit:
		if(b)
			consider_flags = false;
	}
	

	

	/*
	 *  *** Methoden zum Lesen des Binärteils, nach Lesen des ASCII-Headers ***
	 */


	/** Methode zum Einlesen des gesamten Binärteils in ein 2D-Array.
	 * @throws IOException */
	public int[][] readAll() throws IOException {

	    int_field = new int[MAX_ROWS][MAX_COLS];

	    // ================================================

	    // Zeilen von S nach N
	    for(int y = 0; y < MAX_ROWS; y++) {
	        // Spalten (einer Zeile) von W nach E
	        for(int x = 0; x < MAX_COLS; x++) {
	            int_field[y][x] = getValue();

	        } // for x
	        //if(y > 0) break; // bis hier manuell
	    } // for y

	    /* Eingelesen: Komposit-Datei kann schon geschlossen
	     * werden, alle Daten im Array. */
	    radolan.close();


	    if(! raw_values)
	        correct_int_field();
	    
	    // 2D-Feld umdrehen (Norden oben):
	    northUp();
	    
	    return int_field;

	} // readAll


	
	/**
	 * Lese-Methode mit Uebergaben von Pixelkoordinaten
	 * 
	 * Aufgaben:
	 * - Beschleunigtes Einlesen nur der weiter zu verarbeitenden/anzuzeigenden
	 *   Inhalte
	 * - Ausgabe des Pixels oder einer Feldgrafik auf der Konsole
	 *
	 * Ziel dieser Methode ist, den Vorgang des Durchlesens der Datei zu
	 * beschleunigen. Das ist aber nur moeglich, wenn nicht das Maximum der
	 * gesamten Datei bestimmt werden soll, da dann alle Pixel gelesen werden
	 * muessen.
	 * Um den Lesevorgang zu beschleunigen, werden hier Zeilen, die ausserhalb
	 * des angeforderten Bereichs liegen in einem Rutsch ueberlesen (s. Variable "block").
	 * Es muessen aber trotzdem die Bytes durchgelesen werden, diese werden aber nicht
	 * weiter verarbeitet.
	 * Damit wird einiges an unnoetigen Array- und Umrechnungsoperationen eingespart.
	 * Ohne diese Beschraenkung muss man rel. lange auf die Auswertung warten.
	 * Zeilen > angefordert fuehrt zum vorzeitigen Verlassen der Schleife.
	 * 
	 * Diese Methode ist als "Nur-Lesen-Methode" konzipiert.
	 * Es sollen hier keine Ausgaben erfolgen.
	 * Ziel ist, wie oben beschrieben, einen Bereichs-Ausschnitt mit den in Frage
	 * kommenden Werten in einem Array bereit zu stellen.
	 * Dieses Array muss dann erst weiter aufbereitet (korrigiert und "in Form
	 * gebracht") werden. Dies soll erst ueber weitere public-Methoden erfolgen.
	 *  
	 * @param y_geg, x_geg, abstand
	 * @throws IOException 
	 */
	public int[][] readArea(Point cp, int[] d) throws IOException {    // cp = center point

	    /* Attribute setzen
	     * (werden hier initial belegt und von weiteren Methoden verwendet) */

	    // Distance (oben, unten, links, rechts)
	    int
	    up		= d[0],
	    down	= d[1],
	    left	= d[2],
	    right	= d[3];

	    // Ausschnittsfeld dimensionieren: (+1 fuer das Stationspixel)
	    int
	    dimUp	 = up + down + 1,		// quasi die Zeilen in Y-Richtung
	    dimRight = left + right + 1;	// die Spalten in X-Richtung

	    // Quadratisches Feld anlegen:
	    int_field = new int[dimUp][dimRight]; // y * x

	    /* Grenzen des zu lesenden Pixel-Rechteckes bestimmen.
	     * Einmal vor Schleife, da Grenzen konstant sind und nicht immer
	     * wieder neu berechnet werden müssen - ausserdem einfacheres Pruefen. */
	    int
	    upperBorder  = cp.y + up, // Von Ausgangspunkt "nach oben" (von 0 in Richtung 899, also +)
	    bottomBorder = cp.y - down, // "nach unten" (von 899 in Richtung 0, also -)
	    leftBorder   = cp.x - left,
	    rightBorder  = cp.x + right;


	    // Alle Bytes einer Zeile:
	    int bytes_pro_zeile = BYTES * MAX_COLS;

	    /* Von 0 ausgehend folgende Anzahl Bytes zu ueberspringen:
	     * Bytes aller gegebenen Zeilen abzueglich des Abstandes.
	     * Beispiel:
	     * y_geg = 500
	     * d_down = 3
	     * bottomBorder = 497
	     * 
	     * Das bedeutet:
	     * y =
	     * 0 ... 495 -> skip
	     * 496 -> skip
	     * 497 -> READ! */

	    long block = bottomBorder * bytes_pro_zeile;
	    //System.out.println("Block = " + block);


	    // Binär-Leseteil


	    if(block > 0) {
	        radolan.skip(block);
	        //System.out.println("Ueberspringe " + block + " Bytes."
	        //		+ " Position ist nun y = " + y);
	    }
	    /* -> Dateizeiger steht nun an Position y_geg (minus Abstand), d.h. man muss
	     * nicht mehr bei 0 beginnen zu lesen: */

	    /* Beginnen bei der "Untergrenze" des zu lesenden Feldes
	     * (es wird quasi "hoch gelesen") und 
	     * neu entstehendes Feld "umindizieren" - bei 0 beginnen. */
	    int y = bottomBorder, y_ras = 0;

	    // Zeilen von S nach N
	    for(; y < MAX_ROWS; y++) { // beginnen bei oben mod. y

	        /* Akt. Zeile noch vor dem eigentlich zu lesenden Teil:
				Ganzen Datenblock lesen (Zeile aller Bytes auf einmal)
				if(y < y_geg - abstand) {
					// Bytes ueberspringen (werden sowieso nicht verwendet)
				    radolan.skip(block);
				    //System.out.println(y + " < " + y_geg + " - " + abstand + ": "
				    //		+ "Ueberlese Block (" + block + ")");
					continue;
				} */

	        /* Zeile > angefordert: Schleife vorzeitig verlassen.
				 (Hieraus res. die Byte-Differenz zw. Gesamtanzahl Bytes zu gelesenen Bytes.) */
	        if(y > upperBorder) {
	            //System.out.println("Einlesen an y = " + y_geg + " + " + abstand
	            //		+ " beendet.");
	            break; // raus aus Binaerteil
	        }

	        int x_ras = 0; // vorsichtshalber von Schleife unabhaengige Variable benutzen

	        // Spalten (einer Zeile) von W nach E
	        for(int x = 0; x < MAX_COLS; x++) {

	            /* Muss hier stehen!
	             * Dateizeiger muss weiter bewegt werden, unabhängig davon, ob Wert
	             * verwendet wird oder nicht. */
	            int value = getValue();

	            /* x ausserhalb des interessanten Bereichs:
	             * ABER !!!: Bytes muessen gelesen werden, um in der Datei
	             * geordnet weiter zu laufen, daher ist die Leseanweisung
	             * UEBER dieser Bedingung wichtig! */
	            if(x < leftBorder || x > rightBorder)
	                continue; // naechstes x

	            /* Wert-Objekt als 'float' (mit Attributen) oder als reinen
	             * Zahlenwert ('int') erzeugen und speichern: */
	            /*
				   	int_field[y_ras][x_ras++] = (h.getFactor() != 1F) // Faktor = 1 = RX. Bei Aend beachten!
				    		? new Pixel(value, y, x)
				    		: new Pixel(value, y, x, false);
				    		// x nach Einspeichern gleich weiterzaehlen
	             */

	            int_field[y_ras][x_ras++] = value;
	            // x nach Einspeichern gleich weiterzählen

	            /*
				    // Kontrolle:
				    if(y == 320 && x == 390) {
				    	Pixel pix = new Pixel(b_tmp, y, x);
				    	System.out.println("Pixel " + pix.getY() + "|" + pix.getX()
				    		+ ", Wert: " + pix.toInt());
				    		//+ " an Pos. [" + y_ras + "|" + (x_ras-1) + "]");
				    	System.out.print(b_tmp[0] + " " + b_tmp[1] + ", ");
				    } */

	        } // for x

	        y_ras++;
	        //if(y > 0) break; // bis hier manuell
	    } // for y


	    // Eingelesen: Komposit-Datei schliessen.
	    radolan.close();


	    if(! raw_values)
	        correct_int_field();

	    /* Hier steht das Feld steht noch in umgekehrter Orientierung im Array.
	     * Das bedeutet, das Lesen wurde mit dem Sueden begonnen und weiter durch
	     * in Richtung Nord. Dabei wurden alle Werte fortlaufend ueber einen
	     * Indexzaehler in das Array gespeichert, also ist die linke untere
	     * Ecke des Feldes an der Stelle y_ras = x_ras = 0.  */

	    // 2D-Feld umdrehen (Norden oben):
	    northUp();

	    // Fertig, belegt und zurückgeben.
	    return int_field; // [y][x]

	} // readArea

	

	/**
	 * "Weiterleitungs-Methode"
	 * 
	 * Leitet auf den nachfolgenden - 3-parametrige Lese-Methode - um.
	 * Ziel ist, nur eine Lese-Methode zu haben, der mit einem direkt
	 * angegebenen Pixel sowie einem zusaetzlich uebergebenen Abstand
	 * um den Mittelpunkt herum umgehen kann.
	 * 
	 * @param y_geg
	 * @param x_geg
	 * @throws IOException 
	 */
	public int[][] readPixel(Point centerPoint) throws IOException {
		int distances[] = new int[] { 0, 0, 0, 0 }; // <- kein Abstand. Wird Null gesetzt.
		
		return readArea(centerPoint, distances);
	}
	
	
	
	/*
	 * Methode bezieht sich auf ein bereits vorher gelesenes Raster
	 */
	
	/** Gibt ein zweidimensionales Pixel-Objekt-Array zurueck.
	 * Die Groesse richtet sich nach dem angeforderten Ausschnitt
	 * aus dem Gesamtgebiet (angegebener Abstand ausgehend vom Mittelpunkt).
	 * 
	 * Diese Methode kann erst dann angewendet werden, wenn vorher
	 * das gesamte Feld belegt wurde (RADOLAN-Datei komplett eingelesen wurde. 
	 * @throws IOException */
/* am 12.03.2019 deaktiviert, da aufgrund der automatischen Drehung de Feldes
 * nach Norden die übergebenen Pixel-Indizes nicht mehr die richtigen Stellen treffen werden.
	
	public int[][] getAreaFromReadRaster(int y_geg, int x_geg, int a) throws IOException {
		
		if(int_field == null) {
			throw new IOException("readArea: 'int_field' wurde nicht belegt (nicht vorher gelesen)!");
		}
		
		
		//abstand = 1; y_geg = 450; x_geg = 450; // fuer RX-Direkttest
		
		/* Ausschnittsfeld dimensionieren:
		 * a Pixel Abstand vom MPunkt (beide Richtungen) + MPunktsfeld: */
/*		int dim = a * 2 + 1;
		
		// Quadratisches Feld anlegen:
		int[][] ras = new int[dim][dim];
		
		// Neues Feld "umindizieren" - bei 0 beginnen:
		int y_ras = 0;
		
		/* Zeilen von N nach S (y nimmt ab)
		 * ------------------- */
/*		for(int y = y_geg + a; y >= y_geg - a; y--) {
			
			int x_ras = 0;
			
			/* Spalten (einer Zeile) von W nach E (x nimmt zu)
			 * ---------------------------------- */
/*			for(int x = x_geg - a; x <= x_geg + a; x++) {
				//System.out.println(y_ras + "|" + x_ras); // Kontrolle
				// Wert-Objekt in neues Feld kopieren:
				ras[y_ras][x_ras++] = int_field[y][x];
				// x nach Einspeichern gleich weiterzaehlen
			} // x
			
			y_ras++;
		
		} // y
		
		
		// Fertig, belegt und zurückgeben:
		return ras; // [y][x]
		
	} // getAreaFromReadRaster
*/
	
	
	/** int field "korrigieren".
	 * Die Reihenfolge der Prüfungen auf die Flags ist entscheidend! */
	private void correct_int_field() {
		
		out("correct_int_field() ...");
		
		int rows = int_field.length;
		int cols = int_field[0].length;
		
		
		// Die Reihenfolge des Durchlaufs ist hier egal, da
		// die Werte nur korrigiert zurückgespeichert werden.
		
		for(int y=0; y < rows; y++) {

			// Spalten (einer Zeile) von W nach E
			for(int x=0; x < cols; x++) {
				
				int raw_value = int_field[y][x];
				
				/* Fehlkennung:
				 * ============
				 * Der Wert fuer eine Fehlkennung (10692) kann alleine oder
				 * auch zusammen mit Clutter gesetzt sein.
				 * Wird dieser Wert erkannt, muss der Wert verworfen
				 * werden, auch wenn andere Flags (wie "Boden") noch "passen".
				 * (Man kann den Wert fuer die Rasterdarstellung aber ruhig
				 * weiter bis auf 0 runterrechnen)
				 * 28.01.2010: Wurde ueber "Boden" platziert, da im "addiff"
				 * fast ausschliesslich die Fehlkennung 10692 (neben 0.0) vorkam,
				 * und ansonsten die Bodenflagpruefung ihren Wert abziehen wuerde,
				 * womit dann die Fehlkennung nicht mehr erkannt wuerde.
				 * Ansonsten muesste man so eine Pruefung evtl. doch ueber
				 * Bereiche durchfuehren. */
				if(raw_value == MISSING_RAW) {
					/* Auf negativen Wert kann einfacher geprueft
					 * werden, als eine evtl. 0 - man kann den Wert dann
					 * einfacher aus weiteren Berechnungen ausschliessen: */
					int_field[y][x] = MISSING_NEG;
					
					// hier fertig: da Fehlwerte häufig vorkommen, kann hier beschleunigt werden.
					continue;
				}
				
				
				int corr_value = raw_value;    // Default: "corr"-Value ist der Roh-Wert
				
				/* Von groß nach klein:
				Alles unabhängig prüfen; es können mehrere Flags "übereinander liegen". */
				
				// Cluttermarkierter Wert (Bit 16, (32768) gesetzt) umrechnen:
				if(raw_value >= BIT16) {
					corr_value = raw_value - BIT16;
					// Variable 'raw_value' für die weiteren Vorgänge anpassen:
					raw_value = corr_value;
				}
				
				
				/* Interpolierte Bodendaten:
				 * -------------------------
				 * -> Bit 13, (4096) gesetzt.
				 * 
				 * Achtung:
				 * Es gab den Fall in einem RW-Produkt, mit einem Radarausfall
				 * Feldberg. Die Werte hatten den Wert 14788.
				 * Dieser setzt sich zusammen aus dem Wert fuer
				 * BODEN + FEHLK (10692 + 4096)
				 * Das Programm gab daraufhin den Hinweis "interpolierte Bodendaten"
				 * und "Fehlkennung" aus.
				 * Dies widerspricht sich eigentlich, da eine Fehlkennung eine
				 * Nichtbelegung anzeigt.
				 * Im Endeffekt ist es eigentlich unerheblich, in welcher
				 * Reihenfolge ab hier geprueft wird, es ist nur wichtig,
				 * bei gesetztem Flag fuer eine Fehlkennung die Werte
				 * nicht mehr weiter zu verwenden (auch wenn sich nun noch
				 * der Wert fuer das Bodenflag abziehen liesse, kann evtl.
				 * bittechnisch gleich "mit" gesetzt sein).
				 * Damit fuer diesen Fall z.B. in Summationen nicht doch noch mit
				 * einer "uebrig gebliebenen" 4096 gerechnet wird, erfolgt die
				 * Pruefung nicht ueber "else if" ("entweder oder"). Der Wert wird
				 * dann wenigstens (hoffentlich) zur Null. */
				if(consider_bit_13  &&  raw_value >= BIT13) {
					corr_value = raw_value - BIT13;
				}
				
	    		int_field[y][x] = corr_value;
			} // cols
		} // rows
	} // correct_int_field()
	
	
	
	
	/* ------------------------------------------------------------
	 * Hilfsmethoden für die Binärverarbeitung
	 * ------------------------------------------------------------ */

	/** 
	 * Record lesen:
	 * Datentyp "char" kann unter Java hierfuer nicht verwendet werden,
	 * da dieser hier standardmaessig 2 Bytes breit ist.
	 * Mit "byte" koennen nur Werte bis < 127 dargestellt werden(?)
	 * Fuer groessere Werte ist eine Typanpassung, wenn diese den
	 * Zahlenbereich eines Byte -128 bis 127 (0x7f) ueberschreiten.
	 * Dennoch kann ein Byte das gegebene Bitmuster annehmen und repraesentiert
	 * dann eine negative Zahl. Wird diese ausgegeben, findet bei println(int)
	 * eine automatische Typanpassung auf ein int statt und die negative Zahl als
	 * byte wird zur negativen int-Zahl. Das bedeutet, dass das Vorzeichen
	 * uebernommen wird. In einigen Faellen (hier) ist es jedoch wuenschenswert,
	 * ein byte vorzeichenlos zu behandeln. Bei der Ausgabe soll dann ein Datenwert
	 * zwischen 0 und 255 herauskommen. Dies kann durch Bitschiebe-/Umrechnungs-
	 * Operationen erreicht werden oder durch die eingebaute Methode
	 * ReadUnsignedByte:
	 * To read an unsigned byte value from file, use
	 * int readUnsignedByte() method of Java DataInputStream class.
	 * This method reads 1 byte from the Message Data buffer,
	 * starting with the byte referred to by DataOffset and returns
	 * it as an Integer (signed 2-byte) integer value in the range 0 to 255.
	 * 
	 * @throws IOException
	 */
	private int getValue() throws IOException {
		
		byte[] bytes = new byte[BYTES]; // tmp. Einlesearray
		
		/* Java verwendet die "Big-Endian-Byteorder", daher Array
		 * "von hinten nach vorne" belegen: */
		for (int i = BYTES-1; i >= 0; i--)
			bytes[i] = (byte)radolan.readUnsignedByte();

		return unsignedShortToInt(bytes); // byte[] -> int
	}
	

	/**
	 * Umrechungsmethode, die ein Byte-Array in eine Dezimalzahl
	 * wandelt:
	 * 
	 * Converting an unsigned byte array to an integer
	 * 
	 * Converts a two byte array to an integer
	 * @param b a byte array of length 2
	* @return an int representing the unsigned short
	*/
	private static int unsignedShortToInt(byte[] b) {

		int i = 0;

		i |= b[0] & 0xFF;
		i <<= 8;
		i |= b[1] & 0xFF;

		return i;
	}



	/** Dreht das original gelesene int-Field um, sodass die Werte im Norden am Anfang des Arrays stehen.
	 * https://stackoverflow.com/questions/13279740/flipping-a-multidimensional-array-java */
	private void northUp() {

	    out("northUp()");

	    if(is_north) {	// nichts zu tun
	        out("  Feld hat schon Nord-Süd-Orientierung!", false);
	        //return int_field;
	    }

	    for(int i=0; i < (int_field.length / 2); i++) {
	        int[] temp = int_field[i];
	        int_field[i] = int_field[int_field.length - i - 1];
	        int_field[int_field.length - i - 1] = temp;
	    }

	    is_north = true;	// merken

	    //return int_field;
	}

	
	
	
	/*
	 * Get
	 */
	
	
	/** Integer-Field zurückgeben */
	public int[][] int_field() {
		return this.int_field;
	}
	
	/** Werte des int-Feldes in ein float-Feld konvertieren. */
	public float[][] float_field() {
		
		if(float_field != null)
			return float_field;
		
		
		out( String.format("float_field(factor=%f) ...", factor) );
		
		
		int rows = int_field.length;
		int cols = int_field[0].length;
		
		float_field = new float[rows][cols];
		
		
		// Die Reihenfolge des Durchlaufs ist hier egal, da die Wertepositionen
		// sich an den Stellen des Originals (int field) orientieren.
		
		for(int y=0; y < rows; y++) {

			// Spalten (einer Zeile) von W nach E
			for(int x=0; x < cols; x++) {
				
				int int_value = int_field[y][x];
				
				/* Hier muss man mit der Konvertierung des Datentyps aufpassen,
				 * im Test wurde sonst aus int 1 nach Anwendung des Faktors schnell 0!
				 * Integer VORHER nach Float konvertieren! */
				float float_value = (float)int_value * factor;
				
	    		float_field[y][x] = float_value;
			} // cols
		} // rows
		
		return float_field;
		
	} // toFloat()
	
	
	/** Gibt den RADOLAN-ASCII-Header - so wie er gelesen wurde - zurück */
	public String header() {
		return header;
	}


	/** Fehler aufgetreten? */
	public boolean hasError() {
		return (error == null) ? false : true;
	}
	
	
	/** Was für ein Fehler? Meldung holen. */
	public String getError() {
		return error;
	}

    
    /** Öffentliche Methode, um die Dateiverbindung von außen schließen zu können.
     * Klassen im gleichen Package können über das protected-Attribut direkt
     * 'radolan.close()' aufrufen; das funktioniert aber nicht für externe Aufrufer.
     * @return
     */
    public void close() {
    	try {
			radolan.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    
    /** Gibt Metadaten zum RADOLAN-File aus */
    public void print_info() {
        //out("print_info():");
        System.out.println("Produkt-Metadaten:");
        System.out.println("--------------------------------------");
        System.out.println("file:    " + file);
        System.out.println("prod_id: " + prod_id);
        System.out.println("GP:      " + MAX_ROWS + "x" + MAX_COLS);
        System.out.println("factor:  " + factor);
        System.out.println("header:  " + header);
        System.out.println("--------------------------------------");
    }
    
    /** Kontrolle der Raster-Orientierung (Norden oben) */
    public void print_orientation_info() {
    	out("print_orientation_info(): is_north = " + is_north);
    }
    
    

} // class

