org.integratedmodelling.engine.geospace.coverage.raster.AbstractRasterCoverage Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (C) 2007, 2014:
*
* - Ferdinando Villa - integratedmodelling.org - any
* other authors listed in @author annotations
*
* All rights reserved. This file is part of the k.LAB software suite, meant to enable
* modular, collaborative, integrated development of interoperable data and model
* components. For details, see http://integratedmodelling.org.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the Affero General Public License Version 3 or any later version.
*
* This program is distributed in the hope that it will be useful, but without any
* warranty; without even the implied warranty of merchantability or fitness for a
* particular purpose. See the Affero General Public License for more details.
*
* You should have received a copy of the Affero General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite
* 330, Boston, MA 02111-1307, USA. The license is also available at:
* https://www.gnu.org/licenses/agpl.html
*******************************************************************************/
package org.integratedmodelling.engine.geospace.coverage.raster;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.media.jai.iterator.RandomIter;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.gce.geotiff.GeoTiffWriter;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.integratedmodelling.common.utils.NumberUtils;
import org.integratedmodelling.engine.geospace.Geospace;
import org.integratedmodelling.engine.geospace.coverage.ICoverage;
import org.integratedmodelling.engine.geospace.extents.Area;
import org.integratedmodelling.engine.geospace.extents.Grid;
import org.integratedmodelling.engine.geospace.gis.ThinklabVectorizer;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabIOException;
import org.integratedmodelling.exceptions.KlabUnsupportedOperationException;
import org.integratedmodelling.exceptions.KlabValidationException;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
public abstract class AbstractRasterCoverage implements ICoverage {
public static final String NODATA_PROPERTY = "raster.nodata";
protected GridCoverage2D coverage = null;
protected CoordinateReferenceSystem crs = null;
// this one is only non-null if the layer was matched to a different extent; in that
// case we
// keep the original data bbox here so we can check if data are outside its bounds.
protected ReferencedEnvelope originalBoundingBox = null;
protected ReferencedEnvelope boundingBox = null;
protected GridGeometry2D gridGeometry = null;
protected GridSampleDimension dimension = null;
protected double[] noData = null;
protected String sourceURL;
protected String layerName;
protected double xCellSize;
protected double yCellSize;
protected RandomIter itera;
protected RenderedImage image = null;
/*
* allows lazy loading of raster image and JAI iterator
*/
protected boolean _loaded = false;
// only for reporting
protected boolean rasterized = false;
public GridCoverage2D getCoverage() {
return coverage;
}
public void setRasterized(boolean b) {
this.rasterized = b;
}
public boolean isRasterized() {
return this.rasterized;
}
/*
* if this is not null, the raster encodes mappings to these values, with each raster
* value mapping to classMappings[value - 1] and 0 representing no data.
*/
String[] classMappings = null;
static GridCoverageFactory rasterFactory = new GridCoverageFactory();
public BoundingBox getBoundingBox() {
return boundingBox;
}
@Override
public ReferencedEnvelope getEnvelope() {
return boundingBox;
}
// @Override
public double[] getNodataValue() {
return this.noData;
}
public void setNodataValue(double d) {
this.noData = new double[] { d };
}
public void writeImage(File outfile, String format) throws KlabIOException {
try {
ImageIO.write(coverage.getRenderedImage(), "png", outfile);
} catch (IOException e) {
throw new KlabIOException(e);
}
}
public int getXCells() {
return getXRangeMax() - getXRangeOffset();
}
public int getYCells() {
return getYRangeMax() - getYRangeOffset();
}
/**
* Check coverage of point within the ORIGINAL data source. Only meaningful for WCS
* coverages at the moment.
*
* @param x
* @param y
* @return true if covered
*/
public boolean isCovered(int x, int y) {
if (this.originalBoundingBox == null)
return true;
double xx = boundingBox.getMinX() + (xCellSize * x) /*
* + (xCellSize/2.0)
*/;
double yy = boundingBox.getMinY() + (yCellSize * (getYCells() - y - 1)) /*
* +
* (yCellSize
* /2.0)
*/;
return originalBoundingBox.contains(xx, yy);
}
/**
* Return the total number of cells in the coverage, including nodata ones.
*
* @return total cells
*/
public int getTotalCells() {
return (getXRangeMax() - getXRangeOffset()) * (getYRangeMax() - getYRangeOffset());
}
/*
* (non-Javadoc)
* @see org.integratedmodelling.geospace.coverage.ICoverage#show()
*/
public void show() {
coverage.show();
}
/**
* Gets the physical position in current coordinates that corresponds to the center of
* the given pixel.
*
* @param x
* @param y
* @return direct position
*/
public DirectPosition2D getPosition(int x, int y) {
double xx = boundingBox.getMinX() + (xCellSize * x) + (xCellSize / 2.0);
double yy = boundingBox.getMinY() + (yCellSize * (getYCells() - y - 1)) + (yCellSize / 2.0);
return new DirectPosition2D(xx, yy);
}
public double getDouble(int x, int y) {
return itera.getSampleDouble(x, y, 0);
}
/**
* Return the value at the given subdivision, either a double or whatever string our
* value maps to if we're classifying.
*/
@Override
public Object getSubdivisionValue(int subdivisionOrder, Area extent) throws KlabValidationException {
int[] xy = ((Grid) extent).getXYOffsets(subdivisionOrder);
/**
* force to NaN if outside of source data coverage. Geoserver does this wrong, so
* we need to take over.
*/
// if (!isCovered(xy[0], xy[1])) {
// return Double.NaN;
// }
if (classMappings == null) {
Double r = itera.getSampleDouble(xy[0], xy[1], 0);
// the second condition is cheeky but will catch most of the nodata and
// none of the good data
if (Double.isNaN(r) || r < -1.0E35 || r > 1.0E35) {
return Double.NaN;
}
if (this.noData != null) {
for (double nd : noData) {
if (NumberUtils.equal(r, nd)) {
return Double.NaN;
}
}
}
return r;
}
int index = itera.getSample(xy[0], xy[1], 0);
return index == 0 ? null
: // the "nodata" of categories
classMappings[index - 1];
}
public double getLatLowerBound() {
return boundingBox.getMinY();
}
public double getLatUpperBound() {
return boundingBox.getMaxY();
}
public double getLonLowerBound() {
return boundingBox.getMinX();
}
public double getLonUpperBound() {
return boundingBox.getMaxX();
}
public double getMaxDataValue() {
return dimension.getMaximumValue();
}
public double getMinDataValue() {
return dimension.getMinimumValue();
}
public double getNoDataValue() {
// TODO check this is OK - it's clearly not
return noData == null ? Double.NaN : noData[0];
}
@Override
public String getCoordinateReferenceSystemCode() throws KlabException {
return Geospace.getCRSIdentifier(crs, false);
}
@Override
public String getSourceUrl() {
return sourceURL;
}
public int getXRangeMax() {
// todo use getEnvelope2D, then who knows
// gridGeometry.getEnvelope2D().getMaximum(0);
return gridGeometry.getGridRange2D().getHigh(0) + 1;
}
public int getXRangeOffset() {
return gridGeometry.getGridRange().getLow(0);
}
public int getYRangeMax() {
return gridGeometry.getGridRange().getHigh(1) + 1;
}
public int getYRangeOffset() {
return gridGeometry.getGridRange().getLow(1);
}
@Override
public String getLayerName() {
return layerName;
}
public boolean matchesExtent(Grid extent) {
return extent.getEnvelope().equals(boundingBox) && getXCells() == extent.getXCells()
&& getYCells() == extent.getYCells() && crs.equals(extent.getCRS());
}
/**
* Vectorize into a vector coverage.
*
* @param arealExtent
* @return converted coverage
* @throws KlabException
*/
public ICoverage convertToVector(Grid arealExtent) throws KlabException {
return ThinklabVectorizer.vectorize(this, arealExtent);
}
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return crs;
}
@Override
public Object write(File f) throws KlabException {
if (!(f.toString().endsWith(".tif") || f.toString().endsWith(".tiff"))) {
throw new KlabUnsupportedOperationException("raster coverage: only GeoTIFF format is supported for now");
}
GeoTiffWriter gtw;
try {
gtw = new GeoTiffWriter(f);
gtw.write(coverage, null);
} catch (IOException e) {
throw new KlabIOException(e);
}
return null;
}
// @Override
public void setName(String covId) {
layerName = covId;
}
public boolean isLoaded() {
return _loaded;
}
}