au.org.ala.layers.intersect.Grid.java Source code

Java tutorial

Introduction

Here is the source code for au.org.ala.layers.intersect.Grid.java

Source

/**************************************************************************
 * Copyright (C) 2010 Atlas of Living Australia
 * All Rights Reserved.
 * <p>
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * <p>
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 ***************************************************************************/
package au.org.ala.layers.intersect;

import au.org.ala.layers.util.SpatialUtil;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

/**
 * Grid.java
 * Created on June 24, 2005, 4:12 PM
 *
 * @author Robert Hijmans, rhijmans@berkeley.edu
 *         <p/>
 *         Updated 15/2/2010, Adam
 *         <p/>
 *         Interface for .gri/.grd files for now
 */
public class Grid { //  implements Serializable

    public static int maxGridsLoaded = 1; //default
    static ArrayList<Grid> all_grids = new ArrayList<Grid>();
    final double noDataValueDefault = -3.4E38;
    public Boolean byteorderLSB = true; // true if file is LSB (Intel)
    public int ncols, nrows;
    public double nodatavalue;
    public Boolean valid;
    public double[] values;
    public double xmin, xmax, ymin, ymax;
    public double xres, yres;
    public String datatype;
    // properties
    public double minval, maxval;
    public String filename;
    public String units;
    public float rescale = 1;
    /**
     * Log4j instance
     */
    protected Logger logger = Logger.getLogger(this.getClass());
    byte nbytes;
    float[] grid_data = null;
    private List<Grid> subgrids = null;
    private boolean subgrid = false;

    /**
     * loads grd for gri file reference
     *
     * @param fname full path and file name without file extension
     *              of .gri and .grd files to open
     */
    public Grid(String fname) { // construct Grid from file
        filename = fname;
        File grifile = new File(filename + ".gri");
        if (!grifile.exists()) {
            grifile = new File(filename + ".GRI");
        }
        File grdfile = new File(filename + ".grd");
        if (!grdfile.exists()) {
            grdfile = new File(filename + ".GRD");
        }
        if (grdfile.exists() && grifile.exists()) {
            readgrd(filename);

            //update xres/yres when xres == 1
            if (xres == 1) {
                xres = (xmax - xmin) / nrows;
                yres = (ymax - ymin) / ncols;
            }
        } else if (grdfile.exists() && grdfile.isDirectory()) {
            //read dir of grid files
            File idx = new File(grdfile.getPath() + File.separator + "index.grd");
            if (!idx.exists()) {
                makeCollectionIndex(grdfile);
            }
            readgrd(grdfile.getPath() + File.separator + "index");
        } else {
            logger.error("cannot find GRID: " + fname);
        }
    }

    Grid(String fname, boolean keepAvailable) { // construct Grid from file
        filename = fname;
        File grifile = new File(filename + ".gri");
        if (!grifile.exists()) {
            grifile = new File(filename + ".GRI");
        }
        File grdfile = new File(filename + ".grd");
        if (!grdfile.exists()) {
            grdfile = new File(filename + ".GRD");
        }
        if (grdfile.exists() && grifile.exists()) {
            readgrd(filename);

            //update xres/yres when xres == 1
            if (xres == 1) {
                xres = (xmax - xmin) / nrows;
                yres = (ymax - ymin) / ncols;
            }
        } else if (grdfile.exists() && grdfile.isDirectory()) {
            //read dir of grid files
            File idx = new File(grdfile.getPath() + File.separator + "index.grd");
            if (!idx.exists()) {
                makeCollectionIndex(grdfile);
            }
            readgrd(grdfile.getPath() + File.separator + "index");
        } else {
            logger.error("Error constructing grid from file: " + fname);
        }

        if (keepAvailable) {
            Grid.addGrid(this);
        }
    }

    public Grid() {
        //empty grid
    }

    static void removeAvailable() {
        synchronized (all_grids) {
            while (all_grids.size() > 0) {
                all_grids.remove(0);
            }
        }
    }

    static void addGrid(Grid g) {
        synchronized (all_grids) {
            for (int i = 0; i < all_grids.size(); i++) {
                if (g.filename.equalsIgnoreCase(all_grids.get(i).filename)) {
                    return;
                }
            }

            if (all_grids.size() == maxGridsLoaded) {
                all_grids.remove(0);
            }
            all_grids.add(g);
        }
    }

    static public Grid getGrid(String filename) {
        synchronized (all_grids) {
            for (int i = 0; i < all_grids.size(); i++) {
                if (filename.equalsIgnoreCase(all_grids.get(i).filename)) {
                    //get and add to the end of grid list
                    Grid g = all_grids.get(i);
                    all_grids.remove(i);
                    all_grids.add(g);
                    return g;
                }
            }
            return new Grid(filename, true);
        }
    }

    static public Grid getLoadedGrid(String filename) {
        synchronized (all_grids) {
            for (int i = 0; i < all_grids.size(); i++) {
                if (filename.equalsIgnoreCase(all_grids.get(i).filename)) {
                    //get and add to the end of grid list
                    Grid g = all_grids.get(i);
                    all_grids.remove(i);
                    all_grids.add(g);
                    return g;
                }
            }
            return null;
        }
    }

    public static Grid getGridStandardized(String filename) {
        synchronized (all_grids) {
            for (int i = 0; i < all_grids.size(); i++) {
                if (filename.equalsIgnoreCase(all_grids.get(i).filename)) {
                    //get and add to the end of grid list
                    Grid g = all_grids.get(i);
                    all_grids.remove(i);
                    all_grids.add(g);
                    return g;
                }
            }

            Grid g = new Grid(filename, true);
            float[] d = g.getGrid();
            double range = g.maxval - g.minval;
            for (int i = 0; i < d.length; i++) {
                d[i] = (float) ((d[i] - g.minval) / range);
            }
            return g;
        }
    }

    private void makeCollectionIndex(File dir) {
        double xmin = 181;
        double xmax = -181;
        double ymin = 91;
        double ymax = -91;
        double minval = Double.NaN;
        double maxval = Double.NaN;
        double nodatavalue = Double.NaN;

        for (File f : dir.listFiles()) {
            if (f.getName().toLowerCase().endsWith(".grd")) {
                try {
                    Grid g = new Grid(f.getPath().substring(0, f.getPath().length() - 4).toString());
                    if (g != null) {
                        if (g.xmin < xmin)
                            xmin = g.xmin;
                        if (g.xmax > xmax)
                            xmax = g.xmax;
                        if (g.ymin < ymin)
                            ymin = g.ymin;
                        if (g.ymax > ymax)
                            ymax = g.ymax;
                        if (Double.isNaN(minval) || minval > g.minval)
                            minval = g.minval;
                        if (Double.isNaN(maxval) || maxval < g.maxval)
                            maxval = g.maxval;
                        nodatavalue = g.nodatavalue;
                    }
                } catch (Exception e) {
                    logger.error("cannot add: " + f.getPath());
                }
            }
        }

        Grid n = new Grid();
        n.writeHeader(dir.getPath() + File.separator + "index", xmin, ymin, xmax, ymax, -1, -1, -1, -1, minval,
                maxval, "GRIDCOLLECTION", String.valueOf(nodatavalue));

    }

    //transform to file position
    public long getcellnumber(double x, double y) {
        if (x < xmin || x > xmax || y < ymin || y > ymax) //handle invalid inputs
        {
            return -1;
        }

        long col = (long) ((x - xmin) / xres);
        long row = ((long) this.nrows) - 1 - (long) ((y - ymin) / yres);

        //limit each to 0 and ncols-1/nrows-1
        if (col < 0) {
            col = 0;
        }
        if (row < 0) {
            row = 0;
        }
        if (col >= ncols) {
            col = ncols - 1;
        }
        if (row >= nrows) {
            row = nrows - 1;
        }
        return (row * ncols + col);
    }

    private void setdatatype(String s) {
        s = s.toUpperCase();

        // Expected from grd file
        if (s.equals("INT1BYTE")) {
            datatype = "BYTE";
        } else if (s.equals("INT2BYTES")) {
            datatype = "SHORT";
        } else if (s.equals("INT4BYTES")) {
            datatype = "INT";
        } else if (s.equals("INT8BYTES")) {
            datatype = "LONG";
        } else if (s.equals("FLT4BYTES")) {
            datatype = "FLOAT";
        } else if (s.equals("FLT8BYTES")) {
            datatype = "DOUBLE";
        } // shorthand for same
        else if (s.equals("INT1B") || s.equals("BYTE")) {
            datatype = "BYTE";
        } else if (s.equals("INT1U") || s.equals("UBYTE")) {
            datatype = "UBYTE";
        } else if (s.equals("INT2B") || s.equals("INT16") || s.equals("INT2S")) {
            datatype = "SHORT";
        } else if (s.equals("INT4B")) {
            datatype = "INT";
        } else if (s.equals("INT8B") || s.equals("INT32")) {
            datatype = "LONG";
        } else if (s.equals("FLT4B") || s.equals("FLOAT32") || s.equals("FLT4S")) {
            datatype = "FLOAT";
        } else if (s.equals("FLT8B")) {
            datatype = "DOUBLE";
        } // if you rather use Java keywords...
        else if (s.equals("BYTE")) {
            datatype = "BYTE";
        } else if (s.equals("SHORT")) {
            datatype = "SHORT";
        } else if (s.equals("INT")) {
            datatype = "INT";
        } else if (s.equals("LONG")) {
            datatype = "LONG";
        } else if (s.equals("FLOAT")) {
            datatype = "FLOAT";
        } else if (s.equals("DOUBLE")) {
            datatype = "DOUBLE";
        } // some backwards compatibility
        else if (s.equals("INTEGER")) {
            datatype = "INT";
        } else if (s.equals("SMALLINT")) {
            datatype = "INT";
        } else if (s.equals("SINGLE")) {
            datatype = "FLOAT";
        } else if (s.equals("REAL")) {
            datatype = "FLOAT";
        } else if (s.equals("GRIDCOLLECTION")) {
            datatype = s;
        } else {
            logger.error("GRID unknown type: " + s);
            datatype = "UNKNOWN";
        }

        if (datatype.equals("BYTE") || datatype.equals("UBYTE")) {
            nbytes = 1;
        } else if (datatype.equals("SHORT")) {
            nbytes = 2;
        } else if (datatype.equals("INT")) {
            nbytes = 4;
        } else if (datatype.equals("LONG")) {
            nbytes = 8;
        } else if (datatype.equals("SINGLE")) {
            nbytes = 4;
        } else if (datatype.equals("DOUBLE")) {
            nbytes = 8;
        } else {
            nbytes = 0;
        }
    }

    private void readgrd(String filename) {
        IniReader ir = null;
        if ((new File(filename + ".grd")).exists()) {
            ir = new IniReader(filename + ".grd");
        } else {
            ir = new IniReader(filename + ".GRD");
        }

        setdatatype(ir.getStringValue("Data", "DataType"));
        maxval = (float) ir.getDoubleValue("Data", "MaxValue");
        minval = (float) ir.getDoubleValue("Data", "MinValue");
        ncols = ir.getIntegerValue("GeoReference", "Columns");
        nrows = ir.getIntegerValue("GeoReference", "Rows");
        xmin = ir.getDoubleValue("GeoReference", "MinX");
        ymin = ir.getDoubleValue("GeoReference", "MinY");
        xmax = ir.getDoubleValue("GeoReference", "MaxX");
        ymax = ir.getDoubleValue("GeoReference", "MaxY");
        xres = ir.getDoubleValue("GeoReference", "ResolutionX");
        yres = ir.getDoubleValue("GeoReference", "ResolutionY");
        if (ir.valueExists("Data", "NoDataValue")) {
            nodatavalue = ir.getDoubleValue("Data", "NoDataValue");
        } else {
            nodatavalue = Double.NaN;
        }

        String s = ir.getStringValue("Data", "ByteOrder");

        byteorderLSB = true;
        if (s != null && s.length() > 0) {
            if (s.equals("MSB")) {
                byteorderLSB = false;
            } // default is windows (LSB), not linux or Java (MSB)
        }

        units = ir.getStringValue("Data", "Units");

        //make a rescale value
        if (units != null && units.startsWith("1/")) {
            try {
                rescale = 1 / Float.parseFloat(units.substring(2, units.indexOf(' ')));
            } catch (Exception e) {
            }
        }
        if (units != null && units.startsWith("x")) {
            try {
                rescale = Float.parseFloat(units.substring(1, units.indexOf(' ')));
            } catch (Exception e) {
            }
        }
        if (rescale != 1) {
            units = units.substring(units.indexOf(' ') + 1);
            maxval *= rescale;
            minval *= rescale;
        }

        //gri is a collection of grids
        boolean hasSubgrids = "GRIDCOLLECTION".equals(ir.getStringValue("Data", "DataType"));

        //for each subgrid
        if (hasSubgrids) {
            subgrids = new ArrayList<Grid>();

            File dir = new File(filename).getParentFile();
            for (File f : dir.listFiles()) {
                if (f.getName().toLowerCase().endsWith(".grd") && !f.getName().equals("index.grd")) {
                    try {
                        Grid g = new Grid(f.getPath().substring(0, f.getPath().length() - 4).toString());
                        if (g != null) {
                            g.subgrid = true;
                            subgrids.add(g);
                        }
                    } catch (Exception e) {
                        logger.error("invalid grid file: " + f.getPath());
                    }
                }
            }
        }
    }

    public float[] getGrid() {
        int maxArrayLength = Integer.MAX_VALUE - 10;

        if (grid_data != null) {
            return grid_data;
        }

        Grid loadedAlready = getLoadedGrid(filename);
        if (loadedAlready != null && loadedAlready.grid_data != null) {
            return loadedAlready.grid_data;
        }

        int length = nrows * ncols;

        float[] ret = new float[length];

        RandomAccessFile afile = null;
        File f2 = new File(filename + ".GRI");

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            byte[] b = new byte[(int) Math.min(afile.length(), maxArrayLength)];

            int i = 0;
            int max = 0;
            int len;
            while ((len = afile.read(b)) > 0) {
                ByteBuffer bb = ByteBuffer.wrap(b);

                if (byteorderLSB) {
                    bb.order(ByteOrder.LITTLE_ENDIAN);
                }

                if (datatype.equalsIgnoreCase("UBYTE")) {
                    max += len;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = bb.get();
                        if (ret[i] < 0) {
                            ret[i] += 256;
                        }
                    }
                } else if (datatype.equalsIgnoreCase("BYTE")) {
                    max += len;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = bb.get();
                    }
                } else if (datatype.equalsIgnoreCase("SHORT")) {
                    max += len / 2;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = bb.getShort();
                    }
                } else if (datatype.equalsIgnoreCase("INT")) {
                    max += len / 4;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = bb.getInt();
                    }
                } else if (datatype.equalsIgnoreCase("LONG")) {
                    max += len / 8;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = bb.getLong();
                    }
                } else if (datatype.equalsIgnoreCase("FLOAT")) {
                    max += len / 4;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = bb.getFloat();
                    }
                } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                    max += len / 8;
                    max = Math.min(max, ret.length);
                    for (; i < max; i++) {
                        ret[i] = (float) bb.getDouble();
                    }
                } else {
                    // / should not happen; catch anyway...
                    max += len / 4;
                    for (; i < max; i++) {
                        ret[i] = Float.NaN;
                    }
                }
            }

            //replace not a number
            for (i = 0; i < length; i++) {
                if ((float) ret[i] == (float) nodatavalue) {
                    ret[i] = Float.NaN;
                } else {
                    ret[i] *= rescale;
                }
            }
        } catch (Exception e) {
            logger.error("An error has occurred - probably a file error", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        grid_data = ret;
        return ret;
    }

    public void getClassInfo(Map<Float, float[]> info) {

        long length = ((long) nrows) * ((long) ncols);

        RandomAccessFile afile = null;
        File f2 = new File(filename + ".GRI");

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            byte[] b = new byte[65536];

            long i = 0;
            long max = 0;
            long len;
            float v;
            float ndv = (float) nodatavalue;

            while ((len = afile.read(b)) > 0) {
                ByteBuffer bb = ByteBuffer.wrap(b);

                if (byteorderLSB) {
                    bb.order(ByteOrder.LITTLE_ENDIAN);
                }

                if (datatype.equalsIgnoreCase("UBYTE")) {
                    max += len;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.get();
                        if (v < 0)
                            v += 256;
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else if (datatype.equalsIgnoreCase("BYTE")) {
                    max += len;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.get();
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else if (datatype.equalsIgnoreCase("SHORT")) {
                    max += len / 2;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getShort();
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else if (datatype.equalsIgnoreCase("INT")) {
                    max += len / 4;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getInt();
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else if (datatype.equalsIgnoreCase("LONG")) {
                    max += len / 8;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getLong();
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else if (datatype.equalsIgnoreCase("FLOAT")) {
                    max += len / 4;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getFloat();
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                    max += len / 8;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = (float) bb.getDouble();
                        if (v != ndv)
                            updatesStats(info, i, v * rescale);
                    }
                } else {
                    max += len / 4;
                    for (; i < max; i++) {
                        // should not happen; catch anyway...
                    }
                }
            }
        } catch (Exception e) {
            logger.error("An error has occurred getting grid class stats", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

    }

    public void replaceValues(Map<Integer, Integer> translation) {

        long length = ((long) nrows) * ((long) ncols);

        Integer minv = null;
        Integer maxv = null;
        for (Integer i : translation.values()) {
            if (minv == null || i < minv)
                minv = i;
            if (maxv == null || i > maxv)
                maxv = i;
        }

        RandomAccessFile afile = null;
        RandomAccessFile out = null;
        File f2 = new File(filename + ".GRI");
        File newGrid = new File(filename + ".gri.new");

        try { //read of random access file can throw an exception
            out = new RandomAccessFile(newGrid, "rw");

            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            byte[] b = new byte[65536];
            byte[] bout = new byte[65536];

            long i = 0;
            long max = 0;
            long len;
            float v;
            float ndv = (float) nodatavalue;

            while ((len = afile.read(b)) > 0) {
                ByteBuffer bb = ByteBuffer.wrap(b);
                ByteBuffer bbout = ByteBuffer.wrap(bout);

                if (byteorderLSB) {
                    bb.order(ByteOrder.LITTLE_ENDIAN);
                    bbout.order(ByteOrder.LITTLE_ENDIAN);
                }

                if (datatype.equalsIgnoreCase("UBYTE")) {
                    throw new Exception("UBYTE translation not supported");
                } else if (datatype.equalsIgnoreCase("BYTE")) {
                    throw new Exception("BYTE translation not supported");
                } else if (datatype.equalsIgnoreCase("SHORT")) {
                    max += len / 2;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getShort();
                        if (v != ndv && translation.get((int) (v * rescale)) == null) {
                            v = v;
                        }
                        if (v != ndv && translation.get((int) (v * rescale)) != null)
                            v = translation.get((int) (v * rescale));
                        bbout.putShort((short) v);
                    }
                } else if (datatype.equalsIgnoreCase("INT")) {
                    max += len / 4;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getInt();
                        if (v != ndv && translation.get((int) (v * rescale)) != null)
                            v = translation.get((int) (v * rescale));
                        bbout.putInt((int) v);
                    }
                } else if (datatype.equalsIgnoreCase("LONG")) {
                    max += len / 8;
                    max = Math.min(max, length);
                    for (; i < max; i++) {
                        v = bb.getLong();
                        if (v != ndv && translation.get((int) (v * rescale)) != null)
                            v = translation.get((int) (v * rescale));
                        bbout.putLong((long) v);
                    }
                } else if (datatype.equalsIgnoreCase("FLOAT")) {
                    throw new Exception("FLOAT translation not supported");
                } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                    throw new Exception("DOUBLE translation not supported");
                } else {
                    max += len / 4;
                    for (; i < max; i++) {
                        // should not happen; catch anyway...
                    }
                }

                out.write(bout, 0, (int) len);
            }

            writeHeader(filename + ".new", xmin, ymin, xmin + xres * ncols, ymin + yres * nrows, xres, yres, nrows,
                    ncols, minv, maxv, datatype, nodatavalue + "");
        } catch (Exception e) {
            logger.error("An error has occurred getting grid class stats", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }

            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

        try {
            if (!new File(filename + ".gri.old").exists())
                FileUtils.moveFile(new File(filename + ".gri"), new File(filename + ".gri.old"));
            if (!new File(filename + ".grd.old").exists())
                FileUtils.moveFile(new File(filename + ".grd"), new File(filename + ".grd.old"));

            FileUtils.moveFile(new File(filename + ".gri.new"), new File(filename + ".gri"));
            FileUtils.moveFile(new File(filename + ".new.grd"), new File(filename + ".grd"));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private void updatesStats(Map<Float, float[]> info, long i, float v) {
        float[] stats;
        if ((stats = info.get(v)) != null) {
            int row = (int) (i / ncols);
            float lng = (float) (xmin + xres * (i % ncols));
            float lat = (float) (ymax - yres * row);

            stats[0] += SpatialUtil.cellArea(yres, ymin + yres * row);
            if (Float.isNaN(stats[1]) || stats[1] > lng)
                stats[1] = lng;
            if (Float.isNaN(stats[2]) || stats[2] > lat)
                stats[2] = lat;
            if (Float.isNaN(stats[3]) || stats[3] < lng + xres)
                stats[3] = (float) (lng + xres);
            if (Float.isNaN(stats[4]) || stats[4] < lat + yres)
                stats[4] = (float) (lat + yres);
        }
    }

    /**
     * Increase sampleEveryNthPoint to return a smaller grid.
     *
     * Grid max and min values may be skipped.
     *
     * This does not used previously cached data.
     *
     * @param sampleEveryNthPoint
     * @return
     */
    public float[] getGrid(int sampleEveryNthPoint) {
        int maxArrayLength = Integer.MAX_VALUE - 10;

        if (subgrids != null) {
            //sample points
            int size = 1000;
            double[][] points = new double[size * size][2];
            int pos = 0;
            for (int i = 0; i < 1000; i++) {
                for (int j = 0; j < 1000; j++) {
                    points[pos][0] = xmin + (xmax - xmin) * j / (double) size;
                    points[pos][1] = ymax - (ymax - ymin) * i / (double) size;
                    pos++;
                }
            }

            return getValues3(points, 64);
        }

        int length = (nrows / sampleEveryNthPoint) * (ncols);

        float[] ret = new float[length];

        RandomAccessFile afile = null;
        File f2 = new File(filename + ".GRI");

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            int sz = (int) Math.min(afile.length() / sampleEveryNthPoint / sampleEveryNthPoint, maxArrayLength);
            sz += 8 - sz % 8;
            byte[] b = new byte[sz];

            long i = 0;
            long max = 0;
            int len;
            while ((len = afile.read(b)) > 0) {
                ByteBuffer bb = ByteBuffer.wrap(b);

                if (byteorderLSB) {
                    bb.order(ByteOrder.LITTLE_ENDIAN);
                }

                if (datatype.equalsIgnoreCase("UBYTE")) {
                    max += len;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / sampleEveryNthPoint)] = bb.get();
                        if (ret[(int) (i / sampleEveryNthPoint)] < 0) {
                            ret[(int) (i / sampleEveryNthPoint)] += 256;
                        }
                    }
                } else if (datatype.equalsIgnoreCase("BYTE")) {
                    max += len;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / sampleEveryNthPoint)] = bb.get();
                    }
                } else if (datatype.equalsIgnoreCase("SHORT")) {
                    max += len / 2;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / sampleEveryNthPoint)] = bb.getShort();
                    }
                } else if (datatype.equalsIgnoreCase("INT")) {
                    max += len / 4;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / sampleEveryNthPoint)] = bb.getInt();
                    }
                } else if (datatype.equalsIgnoreCase("LONG")) {
                    max += len / 8;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / sampleEveryNthPoint)] = bb.getLong();
                    }
                } else if (datatype.equalsIgnoreCase("FLOAT")) {
                    max += len / 4;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / sampleEveryNthPoint)] = bb.getFloat();
                    }
                } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                    max += len / 8;
                    max = Math.min(max, ret.length * (long) sampleEveryNthPoint);
                    for (; i < max; i++) {
                        ret[(int) (i / (long) sampleEveryNthPoint)] = (float) bb.getDouble();
                    }
                } else {
                    // / should not happen; catch anyway...
                    max += len / 4;
                    for (; i < max; i++) {
                        ret[(int) (i / (long) sampleEveryNthPoint)] = Float.NaN;
                    }
                }
            }

            //replace not a number
            for (i = 0; i < length; i++) {
                if ((float) ret[(int) i] == (float) nodatavalue) {
                    ret[(int) i] = Float.NaN;
                } else {
                    ret[(int) i] *= rescale;
                }
            }
        } catch (Exception e) {
            logger.error("An error has occurred - probably a file error", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        grid_data = ret;
        return ret;
    }

    /**
     * for DomainGenerator
     * <p/>
     * writes out a list of double (same as getGrid() returns) to a file
     * <p/>
     * byteorderlsb
     * data type, FLOAT
     *
     * @param newfilename
     * @param dfiltered
     */
    public void writeGrid(String newfilename, int[] dfiltered, double xmin, double ymin, double xmax, double ymax,
            double xres, double yres, int nrows, int ncols) {
        int size, i, length = dfiltered.length;
        double maxvalue = Integer.MAX_VALUE * -1;
        double minvalue = Integer.MAX_VALUE;

        //write data as whole file
        RandomAccessFile afile = null;
        try { //read of random access file can throw an exception
            afile = new RandomAccessFile(newfilename + ".gri", "rw");

            size = 4;
            byte[] b = new byte[size * length];
            ByteBuffer bb = ByteBuffer.wrap(b);

            if (byteorderLSB) {
                bb.order(ByteOrder.LITTLE_ENDIAN);
            } else {
                bb.order(ByteOrder.BIG_ENDIAN);
            }
            for (i = 0; i < length; i++) {
                bb.putInt(dfiltered[i]);
            }

            afile.write(b);
        } catch (Exception e) {
            logger.error("error writing grid file", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

        writeHeader(newfilename, xmin, ymin, xmin + xres * ncols, ymin + yres * nrows, xres, yres, nrows, ncols,
                minvalue, maxvalue, "INT4BYTES", "-9999");

    }

    /**
     * for grid cutter
     * <p/>
     * writes out a list of double (same as getGrid() returns) to a file
     * <p/>
     * byteorderlsb
     * data type, FLOAT
     *
     * @param newfilename
     * @param dfiltered
     */
    public void writeGrid(String newfilename, double[] dfiltered, double xmin, double ymin, double xmax,
            double ymax, double xres, double yres, int nrows, int ncols) {
        int size, i, length = dfiltered.length;
        double maxvalue = Double.MAX_VALUE * -1;
        double minvalue = Double.MAX_VALUE;

        //write data as whole file
        RandomAccessFile afile = null;
        try { //read of random access file can throw an exception
            afile = new RandomAccessFile(newfilename + ".gri", "rw");

            size = 4;
            byte[] b = new byte[size * length];
            ByteBuffer bb = ByteBuffer.wrap(b);

            if (byteorderLSB) {
                bb.order(ByteOrder.LITTLE_ENDIAN);
            } else {
                bb.order(ByteOrder.BIG_ENDIAN);
            }
            for (i = 0; i < length; i++) {
                if (Double.isNaN(dfiltered[i])) {
                    bb.putFloat((float) noDataValueDefault);
                } else {
                    if (minvalue > dfiltered[i]) {
                        minvalue = dfiltered[i];
                    }
                    if (maxvalue < dfiltered[i]) {
                        maxvalue = dfiltered[i];
                    }
                    bb.putFloat((float) dfiltered[i]);
                }
            }

            afile.write(b);
        } catch (Exception e) {
            logger.error("error writing grid file", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

        writeHeader(newfilename, xmin, ymin, xmin + xres * ncols, ymin + yres * nrows, xres, yres, nrows, ncols,
                minvalue, maxvalue, "FLT4BYTES", String.valueOf(noDataValueDefault));
    }

    public void writeGrid(String newfilename, float[] dfiltered, double xmin, double ymin, double xmax, double ymax,
            double xres, double yres, int nrows, int ncols) {
        int size, i, length = dfiltered.length;
        double maxvalue = Double.MAX_VALUE * -1;
        double minvalue = Double.MAX_VALUE;

        //write data as whole file
        RandomAccessFile afile = null;
        try { //read of random access file can throw an exception
            afile = new RandomAccessFile(newfilename + ".gri", "rw");

            size = 4;
            byte[] b = new byte[size * length];
            ByteBuffer bb = ByteBuffer.wrap(b);

            if (byteorderLSB) {
                bb.order(ByteOrder.LITTLE_ENDIAN);
            } else {
                bb.order(ByteOrder.BIG_ENDIAN);
            }
            for (i = 0; i < length; i++) {
                if (Double.isNaN(dfiltered[i])) {
                    bb.putFloat((float) noDataValueDefault);
                } else {
                    if (minvalue > dfiltered[i]) {
                        minvalue = dfiltered[i];
                    }
                    if (maxvalue < dfiltered[i]) {
                        maxvalue = dfiltered[i];
                    }
                    bb.putFloat((float) dfiltered[i]);
                }
            }

            afile.write(b);
        } catch (Exception e) {
            logger.error("error writing grid file", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

        writeHeader(newfilename, xmin, ymin, xmin + xres * ncols, ymin + yres * nrows, xres, yres, nrows, ncols,
                minvalue, maxvalue, "FLT4BYTES", String.valueOf(noDataValueDefault));

    }

    public void writeHeader(String newfilename, double xmin, double ymin, double xmax, double ymax, double xres,
            double yres, int nrows, int ncols, double minvalue, double maxvalue) {
        writeHeader(newfilename, xmin, ymin, xmax, ymax, xres, yres, nrows, ncols, minvalue, maxvalue, "FLT4BYTES",
                String.valueOf(noDataValueDefault));
    }

    public void writeHeader(String newfilename, double xmin, double ymin, double xmax, double ymax, double xres,
            double yres, int nrows, int ncols, double minvalue, double maxvalue, String datatype, String nodata) {
        FileWriter fw = null;
        try {
            fw = new FileWriter(newfilename + ".grd");

            fw.append("[General]");
            fw.append("\r\n").append("Title=").append(newfilename);
            fw.append("\r\n").append("[GeoReference]");
            fw.append("\r\n").append("Projection=GEOGRAPHIC");
            fw.append("\r\n").append("Datum=WGS84");
            fw.append("\r\n").append("Mapunits=DEGREES");
            fw.append("\r\n").append("Columns=").append(String.valueOf(ncols));
            fw.append("\r\n").append("Rows=").append(String.valueOf(nrows));
            fw.append("\r\n").append("MinX=").append(String.format("%.2f", xmin));
            fw.append("\r\n").append("MaxX=").append(String.format("%.2f", xmax));
            fw.append("\r\n").append("MinY=").append(String.format("%.2f", ymin));
            fw.append("\r\n").append("MaxY=").append(String.format("%.2f", ymax));
            fw.append("\r\n").append("ResolutionX=").append(String.valueOf(xres));
            fw.append("\r\n").append("ResolutionY=").append(String.valueOf(yres));
            fw.append("\r\n").append("[Data]");
            fw.append("\r\n").append("DataType=" + datatype);
            fw.append("\r\n").append("MinValue=").append(String.valueOf(minvalue));
            fw.append("\r\n").append("MaxValue=").append(String.valueOf(maxvalue));
            fw.append("\r\n").append("NoDataValue=").append(nodata);
            fw.append("\r\n").append("Transparent=0");
            fw.flush();
        } catch (Exception e) {
            logger.error("error writing grid file header", e);

        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * do get values of grid for provided points.
     * <p/>
     * loads whole grid file as double[] in process
     *
     * @param points
     * @return
     */
    public float[] getValues2(double[][] points) {
        if (points == null || points.length == 0) {
            return null;
        }

        if (subgrid)
            return getValues3(points, Math.min(1024 * 1024, 64 * points.length));

        //init output structure
        float[] ret = new float[points.length];

        //load whole grid
        float[] grid = getGrid();
        int glen = grid.length;
        int length = points.length;
        int i, pos;

        //points loop
        for (i = 0; i < length; i++) {
            pos = (int) getcellnumber(points[i][0], points[i][1]);
            if (pos >= 0 && pos < glen) {
                ret[i] = grid[pos];
            } else {
                ret[i] = Float.NaN;
            }
        }

        return ret;
    }

    float[] getGrid(double xmin, double ymin, double xmax, double ymax) {
        //expects largest y at the top
        //expects input ranges inside of grid ranges

        int width = (int) ((xmax - xmin) / xres);
        int height = (int) ((ymax - ymin) / yres);
        int startx = (int) ((xmin - this.xmin) / xres);
        int endx = startx + width;
        int starty = (int) ((ymin - this.ymin) / yres);
        //int endy = starty + height;

        int length = width * height;

        float[] ret = new float[length];
        int pos = 0;

        int i;
        RandomAccessFile afile = null;
        File f2 = new File(filename + ".GRI");

        int size = 4;
        if (datatype.equals("BYTE") || datatype.equals("UBYTE")) {
            size = 1;
        } else if (datatype.equals("SHORT")) {
            size = 2;
        } else if (datatype.equals("INT")) {
            size = 4;
        } else if (datatype.equals("LONG")) {
            size = 8;
        } else if (datatype.equals("FLOAT")) {
            size = 4;
        } else if (datatype.equals("DOUBLE")) {
            size = 8;
        }

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            //seek to first raster
            afile.seek(((long) this.ncols) * starty * size);

            //read relevant rasters
            int readSize = this.ncols * height * size;
            int readLen = this.ncols * height;
            byte[] b = new byte[readSize];
            afile.read(b);
            ByteBuffer bb = ByteBuffer.wrap(b);

            if (byteorderLSB) {
                bb.order(ByteOrder.LITTLE_ENDIAN);
            }

            if (datatype.equalsIgnoreCase("BYTE")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.get();
                    } else {
                        ret[pos++] = bb.get();
                    }
                }
            } else if (datatype.equalsIgnoreCase("UBYTE")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.get();
                    } else {
                        ret[pos] = bb.get();
                        if (ret[pos] < 0) {
                            ret[pos] += 256;
                        }
                        pos++;
                    }
                }
            } else if (datatype.equalsIgnoreCase("SHORT")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.getShort();
                    } else {
                        ret[pos++] = bb.getShort();
                    }
                }
            } else if (datatype.equalsIgnoreCase("INT")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.getInt();
                    } else {
                        ret[pos++] = bb.getInt();
                    }
                }
            } else if (datatype.equalsIgnoreCase("LONG")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.getLong();
                    } else {
                        ret[pos++] = bb.getLong();
                    }
                }
            } else if (datatype.equalsIgnoreCase("FLOAT")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.getFloat();
                    } else {
                        ret[pos++] = bb.getFloat();
                    }
                }
            } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                for (i = 0; i < readLen; i++) {
                    int x = i % this.ncols;
                    if (x < startx || x >= endx) {
                        bb.getDouble();
                    } else {
                        ret[pos++] = (float) bb.getDouble();
                    }
                }
            } else {
                // / should not happen; catch anyway...
                for (i = 0; i < length; i++) {
                    ret[i] = Float.NaN;
                }
            }
            //replace not a number
            for (i = 0; i < length; i++) {
                if ((float) ret[i] == (float) nodatavalue) {
                    ret[i] = Float.NaN;
                } else {
                    ret[i] *= rescale;
                }
            }
        } catch (Exception e) {
            logger.error("GRID: " + e.toString(), e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        grid_data = ret;
        return ret;
    }

    public void printMinMax() {
        float min = Float.MAX_VALUE;
        float max = -1 * Float.MAX_VALUE;
        float[] data = this.getGrid();
        int numMissing = 0;
        for (float d : data) {
            if (Float.isNaN(d)) {
                numMissing++;
            }
            if (d < min) {
                min = d;
            }
            if (d > max) {
                max = d;
            }
        }
        if (min != this.minval || max != this.maxval) {
            logger.error(this.filename + " ERR header(" + this.minval + " " + this.maxval + ") actual(" + min + " "
                    + max + ") number missing(" + numMissing + " of " + data.length + ")");
        } else {
            logger.error(this.filename + " OK header(" + this.minval + " " + this.maxval + ") number missing("
                    + numMissing + " of " + data.length + ")");
        }
    }

    /**
     * @param points input array for longitude and latitude
     *               double[number_of_points][2]
     * @return array of .gri file values corresponding to the
     * points provided
     */
    public float[] getValues(double[][] points) {

        //confirm inputs since they come from somewhere else
        if (points == null || points.length == 0) {
            return null;
        }

        //use preloaded grid data if available
        Grid g = Grid.getLoadedGrid(filename);
        if (g != null) {
            return g.getValues2(points);
        }

        if (subgrids != null) {
            return getValues3(points, Math.min(1024 * 1024, 64 * points.length));
        }

        float[] ret = new float[points.length];

        int length = points.length;
        long size;
        int i, pos;
        byte[] b;
        RandomAccessFile afile = null;
        File f2 = new File(filename + ".GRI");

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            if (datatype.equalsIgnoreCase("BYTE")) {
                size = 1;
                b = new byte[(int) size];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        ret[i] = afile.readByte();
                    } else {
                        ret[i] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("UBYTE")) {
                size = 1;
                b = new byte[(int) size];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        ret[i] = afile.readByte();
                        if (ret[i] < 0) {
                            ret[i] += 256;
                        }
                    } else {
                        ret[i] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("SHORT")) {
                size = 2;
                b = new byte[(int) size];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        afile.read(b);
                        if (byteorderLSB) {
                            ret[i] = (short) (((0xFF & b[1]) << 8) | (b[0] & 0xFF));
                        } else {
                            ret[i] = (short) (((0xFF & b[0]) << 8) | (b[1] & 0xFF));
                        }
                        //ret[i] = afile.readShort();
                    } else {
                        ret[i] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("INT")) {
                size = 4;
                b = new byte[(int) size];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        afile.read(b);
                        if (byteorderLSB) {
                            ret[i] = ((0xFF & b[3]) << 24)
                                    | ((0xFF & b[2]) << 16) + ((0xFF & b[1]) << 8) + (b[0] & 0xFF);
                        } else {
                            ret[i] = ((0xFF & b[0]) << 24)
                                    | ((0xFF & b[1]) << 16) + ((0xFF & b[2]) << 8) + ((0xFF & b[3]) & 0xFF);
                        }
                        //ret[i] = afile.readInt();
                    } else {
                        ret[i] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("LONG")) {
                size = 8;
                b = new byte[(int) size];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        afile.read(b);
                        if (byteorderLSB) {
                            ret[i] = ((long) (0xFF & b[7]) << 56) + ((long) (0xFF & b[6]) << 48)
                                    + ((long) (0xFF & b[5]) << 40) + ((long) (0xFF & b[4]) << 32)
                                    + ((long) (0xFF & b[3]) << 24) + ((long) (0xFF & b[2]) << 16)
                                    + ((long) (0xFF & b[1]) << 8) + (0xFF & b[0]);
                        } else {
                            ret[i] = ((long) (0xFF & b[0]) << 56) + ((long) (0xFF & b[1]) << 48)
                                    + ((long) (0xFF & b[2]) << 40) + ((long) (0xFF & b[3]) << 32)
                                    + ((long) (0xFF & b[4]) << 24) + ((long) (0xFF & b[5]) << 16)
                                    + ((long) (0xFF & b[6]) << 8) + (0xFF & b[7]);
                        }
                        //ret[i] = afile.readLong();
                    } else {
                        ret[i] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("FLOAT")) {
                size = 4;
                b = new byte[(int) size];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        afile.read(b);
                        ByteBuffer bb = ByteBuffer.wrap(b);
                        if (byteorderLSB) {
                            bb.order(ByteOrder.LITTLE_ENDIAN);
                        }
                        ret[i] = bb.getFloat();
                    } else {
                        ret[i] = Float.NaN;
                    }

                }
            } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                size = 8;
                b = new byte[8];
                for (i = 0; i < length; i++) {
                    pos = (int) getcellnumber(points[i][0], points[i][1]);
                    if (pos >= 0) {
                        afile.seek(pos * size);
                        afile.read(b);
                        ByteBuffer bb = ByteBuffer.wrap(b);
                        if (byteorderLSB) {
                            bb.order(ByteOrder.LITTLE_ENDIAN);
                        }
                        ret[i] = (float) bb.getDouble();

                        //ret[i] = afile.readFloat();
                    } else {
                        ret[i] = Float.NaN;
                    }
                }
            } else {
                logger.error("datatype not supported in Grid.getValues: " + datatype);
                // / should not happen; catch anyway...
                for (i = 0; i < length; i++) {
                    ret[i] = Float.NaN;
                }
            }
            //replace not a number
            for (i = 0; i < length; i++) {
                if ((float) ret[i] == (float) nodatavalue) {
                    ret[i] = Float.NaN;
                } else {
                    ret[i] *= rescale;
                }
            }
        } catch (Exception e) {
            logger.error("error getting grid file values", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        return ret;
    }

    /**
     * @param points input array for longitude and latitude
     *               double[number_of_points][2] and sorted latitude then longitude
     * @return array of .gri file values corresponding to the
     * points provided
     */
    public float[] getValues3(double[][] points, int bufferSize) {
        //confirm inputs since they come from somewhere else
        if (points == null || points.length == 0) {
            return null;
        }

        if (subgrids != null) {
            return getValuesSubgrids(points, bufferSize);
        }

        //use preloaded grid data if available
        Grid g = Grid.getLoadedGrid(filename);
        if (g != null && g.grid_data != null) {
            return g.getValues2(points);
        }

        int length = points.length;
        int size, i;
        byte[] b;

        RandomAccessFile afile = null;

        File f2 = new File(filename + ".GRI");

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            //do not cache subgrids (using getValues2)
            if (!subgrid && afile.length() < 80 * 1024 * 1024) {
                try {
                    afile.close();
                    afile = null;
                } catch (Exception e) {
                }
                return getValues2(points);
            }

            byte[] buffer = new byte[bufferSize]; //must be multiple of 64
            Long bufferOffset = afile.length();

            float[] ret = new float[points.length];

            //get cell numbers
            long[][] cells = new long[points.length][2];
            for (int j = 0; j < points.length; j++) {
                if (Double.isNaN(points[j][0]) || Double.isNaN(points[j][1])) {
                    cells[j][0] = -1;
                    cells[j][1] = j;
                } else {
                    cells[j][0] = getcellnumber(points[j][0], points[j][1]);
                    cells[j][1] = j;
                }
            }
            java.util.Arrays.sort(cells, new Comparator<long[]>() {

                @Override
                public int compare(long[] o1, long[] o2) {
                    if (o1[0] == o2[0]) {
                        return o1[1] > o2[1] ? 1 : -1;
                    } else {
                        return o1[0] > o2[0] ? 1 : -1;
                    }
                }
            });

            if (datatype.equalsIgnoreCase("BYTE")) {
                size = 1;
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        ret[(int) cells[i][1]] = getByte(afile, buffer, bufferOffset, cells[i][0] * size);
                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("UBYTE")) {
                size = 1;
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        ret[(int) cells[i][1]] = getByte(afile, buffer, bufferOffset, cells[i][0] * size);
                        if (ret[(int) cells[i][1]] < 0) {
                            ret[(int) cells[i][1]] += 256;
                        }
                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("SHORT")) {
                size = 2;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        bufferOffset = getBytes(afile, buffer, bufferOffset, cells[i][0] * (long) size, b);
                        if (byteorderLSB) {
                            ret[(int) cells[i][1]] = (short) (((0xFF & b[1]) << 8) | (b[0] & 0xFF));
                        } else {
                            ret[(int) cells[i][1]] = (short) (((0xFF & b[0]) << 8) | (b[1] & 0xFF));
                        }
                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("INT")) {
                size = 4;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        bufferOffset = getBytes(afile, buffer, bufferOffset, cells[i][0] * (long) size, b);
                        if (byteorderLSB) {
                            ret[(int) cells[i][1]] = ((0xFF & b[3]) << 24)
                                    | ((0xFF & b[2]) << 16) + ((0xFF & b[1]) << 8) + (b[0] & 0xFF);
                        } else {
                            ret[(int) cells[i][1]] = ((0xFF & b[0]) << 24)
                                    | ((0xFF & b[1]) << 16) + ((0xFF & b[2]) << 8) + ((0xFF & b[3]) & 0xFF);
                        }
                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("LONG")) {
                size = 8;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        bufferOffset = getBytes(afile, buffer, bufferOffset, cells[i][0] * (long) size, b);
                        if (byteorderLSB) {
                            ret[(int) cells[i][1]] = ((long) (0xFF & b[7]) << 56) + ((long) (0xFF & b[6]) << 48)
                                    + ((long) (0xFF & b[5]) << 40) + ((long) (0xFF & b[4]) << 32)
                                    + ((long) (0xFF & b[3]) << 24) + ((long) (0xFF & b[2]) << 16)
                                    + ((long) (0xFF & b[1]) << 8) + (0xFF & b[0]);
                        } else {
                            ret[(int) cells[i][1]] = ((long) (0xFF & b[0]) << 56) + ((long) (0xFF & b[1]) << 48)
                                    + ((long) (0xFF & b[2]) << 40) + ((long) (0xFF & b[3]) << 32)
                                    + ((long) (0xFF & b[4]) << 24) + ((long) (0xFF & b[5]) << 16)
                                    + ((long) (0xFF & b[6]) << 8) + (0xFF & b[7]);
                        }
                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }
                }
            } else if (datatype.equalsIgnoreCase("FLOAT")) {
                size = 4;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        bufferOffset = getBytes(afile, buffer, bufferOffset, cells[i][0] * (long) size, b);
                        ByteBuffer bb = ByteBuffer.wrap(b);
                        if (byteorderLSB) {
                            bb.order(ByteOrder.LITTLE_ENDIAN);
                        }
                        ret[(int) cells[i][1]] = bb.getFloat();
                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }

                }
            } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                size = 8;
                b = new byte[8];
                for (i = 0; i < length; i++) {
                    if (i > 0 && cells[i - 1][0] == cells[i][0]) {
                        ret[(int) cells[i][1]] = ret[(int) cells[i - 1][1]];
                        continue;
                    }
                    if (cells[i][0] >= 0) {
                        getBytes(afile, buffer, bufferOffset, cells[i][0] * (long) size, b);
                        ByteBuffer bb = ByteBuffer.wrap(b);
                        if (byteorderLSB) {
                            bb.order(ByteOrder.LITTLE_ENDIAN);
                        }
                        ret[(int) cells[i][1]] = (float) bb.getDouble();

                    } else {
                        ret[(int) cells[i][1]] = Float.NaN;
                    }
                }
            } else {
                logger.error("datatype not supported in Grid.getValues: " + datatype);
                // / should not happen; catch anyway...
                for (i = 0; i < length; i++) {
                    ret[i] = Float.NaN;
                }
            }
            //replace not a number
            for (i = 0; i < length; i++) {
                if ((float) ret[i] == (float) nodatavalue) {
                    ret[i] = Float.NaN;
                } else {
                    ret[i] *= rescale;
                }
            }
            return ret;
        } catch (Exception e) {
            logger.error("error getting grid file values", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        return null;
    }

    private float[] getValuesSubgrids(double[][] points, int bufferSize) {
        int[] subgrid = new int[points.length];
        int[] subgridCounts = new int[subgrids.size()];

        //match points to subgrids
        int anySubgrid = -1;
        for (int i = 0; i < points.length; i++) {
            subgrid[i] = -1;
            for (int j = 0; j < subgrids.size(); j++) {
                Grid g = subgrids.get(j);
                if (g.xmin <= points[i][0] && g.xmax >= points[i][0] && g.ymin <= points[i][1]
                        && g.ymax >= points[i][1]) {
                    subgrid[i] = j;
                    subgridCounts[j]++;
                    anySubgrid = j;
                    break;
                }
            }
        }

        //do not need to split because only 1 subgrid
        if (anySubgrid >= 0 && subgridCounts[anySubgrid] == points.length) {
            return subgrids.get(anySubgrid).getValues3(points, bufferSize);
        } else {
            //intersect
            float[] values = new float[points.length];
            for (int i = 0; i < values.length; i++) {
                values[i] = Float.NaN;
            }

            //no intersection
            if (anySubgrid == -1) {
                return values;
            }

            for (int i = 0; i < subgridCounts.length; i++) {
                if (subgridCounts[i] > 0) {
                    //build new points array
                    double[][] newpoints = new double[subgridCounts[i]][2];
                    int p = 0;
                    for (int j = 0; j < points.length; j++) {
                        if (subgrid[j] == i) {
                            newpoints[p] = points[j];
                            p++;
                        }
                    }

                    //intersect
                    float[] subValues = subgrids.get(i).getValues3(newpoints, bufferSize);

                    //write back intersect values
                    p = 0;
                    for (int j = 0; j < points.length; j++) {
                        if (subgrid[j] == i) {
                            values[j] = subValues[p];
                            p++;
                        }
                    }
                }
            }

            return values;
        }
    }

    /*
     * Cut a one grid against the missing values of another.
     *
     * They must be aligned.
     */
    public void mergeMissingValues(Grid sourceOfMissingValues, boolean hideMissing) {
        float[] cells = sourceOfMissingValues.getGrid();

        float[] actual = getGrid();

        int length = actual.length;

        int i;
        RandomAccessFile afile = null;
        File f2 = new File(filename + ".GRI");

        try { //read of random access file can throw an exception
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "rw");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "rw");
            }

            byte[] b = new byte[(int) afile.length()];

            ByteBuffer bb = ByteBuffer.wrap(b);

            if (byteorderLSB) {
                bb.order(ByteOrder.LITTLE_ENDIAN);
            }

            afile.seek(0);

            if (datatype.equalsIgnoreCase("UBYTE")) {
                for (i = 0; i < length; i++) {
                    if (hideMissing == Float.isNaN(cells[i])) {
                        if (nodatavalue >= 128) {
                            bb.put((byte) (nodatavalue - 256));
                        } else {
                            bb.put((byte) nodatavalue);
                        }
                    } else {
                        if (actual[i] >= 128) {
                            bb.put((byte) (actual[i] - 256));
                        } else {
                            bb.put((byte) actual[i]);
                        }
                    }
                }
            } else if (datatype.equalsIgnoreCase("BYTE")) {
                for (i = 0; i < length; i++) {
                    bb.put((byte) actual[i]);
                }
            } else if (datatype.equalsIgnoreCase("SHORT")) {
                for (i = 0; i < length; i++) {
                    if (hideMissing == Float.isNaN(cells[i])) {
                        bb.putShort((short) nodatavalue);
                    } else {
                        bb.putShort((short) actual[i]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("INT")) {
                for (i = 0; i < length; i++) {
                    if (hideMissing == Float.isNaN(cells[i])) {
                        bb.putInt((int) nodatavalue);
                    } else {
                        bb.putInt((int) actual[i]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("LONG")) {
                for (i = 0; i < length; i++) {
                    if (hideMissing == Float.isNaN(cells[i])) {
                        bb.putLong((long) nodatavalue);
                    } else {
                        bb.putLong((long) actual[i]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("FLOAT")) {
                for (i = 0; i < length; i++) {
                    if (hideMissing == Float.isNaN(cells[i])) {
                        bb.putFloat((float) nodatavalue);
                    } else {
                        bb.putFloat(actual[i]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                for (i = 0; i < length; i++) {
                    if (hideMissing == Float.isNaN(cells[i])) {
                        bb.putDouble((double) nodatavalue);
                    } else {
                        bb.putDouble((double) actual[i]);
                    }
                }
            } else {
                // should not happen
                logger.error("unsupported grid data type: " + datatype);
            }

            afile.write(bb.array());
        } catch (Exception e) {
            logger.error("error getting grid file values", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * buffering on top of RandomAccessFile
     */
    private byte getByte(RandomAccessFile raf, byte[] buffer, Long bufferOffset, long seekTo) throws IOException {
        long relativePos = seekTo - bufferOffset;
        if (relativePos < 0) {
            raf.seek(seekTo);
            bufferOffset = seekTo;
            raf.read(buffer);
            return buffer[0];
        } else if (relativePos >= 0 && relativePos < buffer.length) {
            return buffer[(int) relativePos];
        } else if (relativePos - buffer.length < buffer.length) {
            bufferOffset += buffer.length;
            raf.read(buffer);
            return buffer[(int) (relativePos - buffer.length)];
        } else {
            raf.seek(seekTo);
            bufferOffset = seekTo;
            raf.read(buffer);
            return buffer[0];
        }
    }

    /**
     * buffering on top of RandomAccessFile
     */
    private Long getBytes(RandomAccessFile raf, byte[] buffer, Long bufferOffset, long seekTo, byte[] dest)
            throws IOException {
        long relativePos = seekTo - bufferOffset;
        if (relativePos < 0) {
            if (seekTo < 0) {
                seekTo = 0;
            }
            raf.seek(seekTo);
            bufferOffset = seekTo;
            raf.read(buffer);
            System.arraycopy(buffer, 0, dest, 0, dest.length);
        } else if (relativePos >= 0 && relativePos < buffer.length) {
            System.arraycopy(buffer, (int) relativePos, dest, 0, dest.length);
        } else if (relativePos - buffer.length < buffer.length) {
            bufferOffset += buffer.length;
            raf.read(buffer);
            int offset = (int) (relativePos - buffer.length);
            System.arraycopy(buffer, offset, dest, 0, dest.length);
        } else {
            raf.seek(seekTo);
            bufferOffset = seekTo;
            raf.read(buffer);
            System.arraycopy(buffer, 0, dest, 0, dest.length);
        }

        return bufferOffset;
    }

    /**
     * @return calculated min and max values of a grid file as float [] where [0] is min and [1] is max.
     */
    public float[] calculatetMinMax() {

        float[] ret = new float[2];
        ret[0] = Float.MAX_VALUE;
        ret[1] = Float.MAX_VALUE * -1;

        long i;
        int size;
        byte[] b;
        RandomAccessFile afile = null;

        try { //read of random access file can throw an exception
            File f2 = new File(filename + ".GRI");
            if (!f2.exists()) {
                afile = new RandomAccessFile(filename + ".gri", "r");
            } else {
                afile = new RandomAccessFile(filename + ".GRI", "r");
            }

            long length = ((long) nrows) * ((long) ncols);
            float f;

            if (datatype.equalsIgnoreCase("BYTE")) {
                size = 1;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    f = afile.readByte();
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("UBYTE")) {
                size = 1;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    f = afile.readByte();
                    if (f < 0) {
                        f += 256;
                    }
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("SHORT")) {
                size = 2;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    afile.read(b);
                    if (byteorderLSB) {
                        f = (short) (((0xFF & b[1]) << 8) | (b[0] & 0xFF));
                    } else {
                        f = (short) (((0xFF & b[0]) << 8) | (b[1] & 0xFF));
                    }
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("INT")) {
                size = 4;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    afile.read(b);
                    if (byteorderLSB) {
                        f = ((0xFF & b[3]) << 24) | ((0xFF & b[2]) << 16) + ((0xFF & b[1]) << 8) + (b[0] & 0xFF);
                    } else {
                        f = ((0xFF & b[0]) << 24)
                                | ((0xFF & b[1]) << 16) + ((0xFF & b[2]) << 8) + ((0xFF & b[3]) & 0xFF);
                    }
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("LONG")) {
                size = 8;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    afile.read(b);
                    if (byteorderLSB) {
                        f = ((long) (0xFF & b[7]) << 56) + ((long) (0xFF & b[6]) << 48)
                                + ((long) (0xFF & b[5]) << 40) + ((long) (0xFF & b[4]) << 32)
                                + ((long) (0xFF & b[3]) << 24) + ((long) (0xFF & b[2]) << 16)
                                + ((long) (0xFF & b[1]) << 8) + (0xFF & b[0]);
                    } else {
                        f = ((long) (0xFF & b[0]) << 56) + ((long) (0xFF & b[1]) << 48)
                                + ((long) (0xFF & b[2]) << 40) + ((long) (0xFF & b[3]) << 32)
                                + ((long) (0xFF & b[4]) << 24) + ((long) (0xFF & b[5]) << 16)
                                + ((long) (0xFF & b[6]) << 8) + (0xFF & b[7]);
                    }
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("FLOAT")) {
                size = 4;
                b = new byte[size];
                for (i = 0; i < length; i++) {
                    afile.read(b);
                    ByteBuffer bb = ByteBuffer.wrap(b);
                    if (byteorderLSB) {
                        bb.order(ByteOrder.LITTLE_ENDIAN);
                    }
                    f = bb.getFloat();
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else if (datatype.equalsIgnoreCase("DOUBLE")) {
                size = 8;
                b = new byte[8];
                for (i = 0; i < length; i++) {
                    afile.read(b);
                    ByteBuffer bb = ByteBuffer.wrap(b);
                    if (byteorderLSB) {
                        bb.order(ByteOrder.LITTLE_ENDIAN);
                    }
                    f = (float) bb.getDouble();
                    if (f != (float) nodatavalue) {
                        ret[0] = Math.min(f * rescale, ret[0]);
                        ret[1] = Math.max(f * rescale, ret[1]);
                    }
                }
            } else {
                logger.error("datatype not supported in Grid.getValues: " + datatype);
            }
        } catch (Exception e) {
            logger.error("error calculating min/max of a grid file", e);
        } finally {
            if (afile != null) {
                try {
                    afile.close();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        return ret;
    }
}