package org.opentripplanner.analyst.batch;
import java.io.File;
import lombok.Setter;
import org.geotools.coverage.grid.GridCoordinates2D;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.gce.geotiff.GeoTiffFormat;
import org.geotools.gce.geotiff.GeoTiffWriteParams;
import org.geotools.gce.geotiff.GeoTiffWriter;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.geometry.DirectPosition;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Individuals should be in a random-access-friendly List implementation in row-major order
*/
public class RasterPopulation extends BasicPopulation {
private static final Logger LOG = LoggerFactory.getLogger(RasterPopulation.class);
/* configuration fields */
@Setter int rows = 200, cols = 200; // these are the raster (gridEnvelope) dimensions
@Setter double left, right, top, bottom; // bounding box values in CRS
@Setter int band = 0; // raster band to read
@Setter double unitySeconds = 0; // scale output values so unity=1. 0 to turn off.
/* derived fields */
protected CoordinateReferenceSystem coverageCRS; // from input raster or config string
protected GridEnvelope2D gridEnvelope; // the envelope for the pixels
protected ReferencedEnvelope refEnvelope; // the envelope in the CRS
protected GridGeometry2D gridGeometry; // relationship between the grid envelope and the CRS envelope
protected MathTransform gridToWGS84; // composed transform from the grid to the CRS to WGS84
protected GridCoverage2D coverage; // null if synthetic (no coverage to load)
@Override
public void writeAppropriateFormat(String outFileName, ResultSet results) {
this.writeGeotiff(outFileName, results);
}
public void setUnityMinutes(double minutes) {
this.unitySeconds = minutes * 60;
}
public void writeGeotiff(String fileName, ResultSet results) {
LOG.info("writing geotiff.");
float[][] imagePixelData = new float[rows][cols];
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
int index = row * cols + col;
float pixel = (float) (results.results[index]);
if (unitySeconds > 0)
pixel /= unitySeconds;
imagePixelData[row][col] = pixel;
}
}
GridCoverage2D coverage = new GridCoverageFactory().create("OTPAnalyst", imagePixelData, refEnvelope);
try {
GeoTiffWriteParams wp = new GeoTiffWriteParams();
wp.setCompressionMode(GeoTiffWriteParams.MODE_EXPLICIT);
wp.setCompressionType("LZW");
ParameterValueGroup params = new GeoTiffFormat().getWriteParameters();
params.parameter(AbstractGridFormat.GEOTOOLS_WRITE_PARAMS.getName().toString()).setValue(wp);
GeoTiffWriter writer = new GeoTiffWriter(new File(fileName));
writer.write(coverage, (GeneralParameterValue[]) params.values().toArray(new GeneralParameterValue[1]));
} catch (Exception e) {
LOG.error("exception while writing geotiff.");
e.printStackTrace();
}
LOG.info("done writing geotiff.");
}
@Override
public void createIndividuals() {
LOG.info("Loading population from raster file {}", sourceFilename);
try {
File rasterFile = new File(sourceFilename);
// determine file format and CRS, then load raster
AbstractGridFormat format = GridFormatFinder.findFormat(rasterFile);
AbstractGridCoverage2DReader reader = format.getReader(rasterFile);
GridCoverage2D coverage = reader.read(null);
this.coverageCRS = coverage.getCoordinateReferenceSystem();
GridGeometry2D gridGeometry = coverage.getGridGeometry();
GridEnvelope2D gridEnvelope = gridGeometry.getGridRange2D();
gridGeometry.getGridToCRS();
// because we may want to produce an empty raster rather than loading one, alongside the coverage we
// store the row/col dimensions and the referenced envelope in the original coordinate reference system.
this.cols = gridEnvelope.width;
this.rows = gridEnvelope.height;
this.createIndividuals0();
} catch (Exception ex) {
throw new IllegalStateException("Error loading population from raster file: ", ex);
}
LOG.info("Done loading raster from file.");
}
/** Shared internal createIndividuals method allowing synthetic subclass to reuse projection code */
protected void createIndividuals0() {
MathTransform tr;
try {
final CoordinateReferenceSystem WGS84 = CRS.decode("EPSG:4326", true);
tr = CRS.findMathTransform(coverageCRS, WGS84);
} catch (Exception e) {
LOG.equals("error creating CRS transform.");
e.printStackTrace();
return;
}
// grid coordinate object to be reused for reading each cell in the raster
GridCoordinates2D coord = new GridCoordinates2D();
// evaluating a raster returns an array of results, in this case 1D
int[] val = new int[1];
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
coord.x = col;
coord.y = row;
try {
// find coordinates for current raster cell in raster CRS
DirectPosition sourcePos = gridGeometry.gridToWorld(coord);
// TODO: we are performing 2 transforms here, it would probably be more efficient to compose
// the grid-to-crs and crs-to-WGS84 transforms into grid-to-WGS84.
// cf. MathTransformFactory and CoordinateOperationFactory
// convert coordinates in raster CRS to WGS84
DirectPosition targetPos = tr.transform(sourcePos, null);
double lon = targetPos.getOrdinate(0);
double lat = targetPos.getOrdinate(1);
// evaluate using grid coordinates, which should be more efficient than using world coordinates
if (coverage != null)
coverage.evaluate(coord, val);
// add this grid cell to the population
String label = row + "_" + col;
Individual individual = new Individual(label, lon, lat, val[band]);
this.addIndividual(individual);
} catch (Exception e) {
LOG.error("error creating individuals for raster");
e.printStackTrace();
return;
}
}
}
}
}