org.integratedmodelling.engine.geospace.extents.Grid Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (C) 2007, 2015:
*
* - 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.extents;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.integratedmodelling.api.modelling.IExtent;
import org.integratedmodelling.api.space.IGrid;
import org.integratedmodelling.api.space.IGridMask;
import org.integratedmodelling.api.space.IShape;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.space.IGeometricShape;
import org.integratedmodelling.common.space.SpaceLocator;
import org.integratedmodelling.common.utils.MiscUtilities;
import org.integratedmodelling.common.vocabulary.Unit;
import org.integratedmodelling.engine.geospace.Geospace;
import org.integratedmodelling.engine.geospace.coverage.raster.RasterActivationLayer;
import org.integratedmodelling.engine.geospace.gis.ThinklabRasterizer;
import org.integratedmodelling.engine.geospace.literals.ShapeValue;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabRuntimeException;
import org.integratedmodelling.exceptions.KlabUnsupportedOperationException;
import org.integratedmodelling.exceptions.KlabValidationException;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
/**
* Cell extents are the extents generated by the Raster conceptual models. Vector
* CMs should be prepared to deal with them too.
*
* A GridExtent may have been defined from a ShapeExtent by rasterizing objects. For this
* reason, we implement ILineageTraceable so that the original objects can be reconstructed.
* This must be transferred to the conceptualized grid in some (preferably elegant) way (TODO).
*
* Provides storage metadata that we float to the models and contexts that contain it, so that
* they can be queried for coverage with another context as parameter.
*
* @author Ferdinando, Ioannis
*
*/
public class Grid extends Area implements IGrid {
/*
* only set in a subgrid of the grid having the parent ID.
*/
int[] offsetInSupergrid = null;
String superGridId = null;
GeometryFactory gFactory = null;
int xDivs = 0;
int yDivs = 0;
double cellLength = 0.0;
double cellHeight = 0.0;
double xOrigin = 0.0;
double yOrigin = 0.0;
IGridMask activationLayer = null;
private double cellHeightMeters;
private double cellWidthMeters;
private double cellAreaMeters = -1.0;
private CoordinateReferenceSystem metersCRS = null;
Geometry boundary = null;
public ShapeValue shape;
public class CellImpl implements Cell {
int x;
int y;
CellImpl(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int getX() {
return x;
}
@Override
public int getY() {
return y;
}
@Override
public Cell N() {
int trow = getYCells() - 1;
if (y < trow) {
return new CellImpl(x, y + 1);
}
return null;
}
@Override
public Cell S() {
if (y > 0) {
return new CellImpl(x, y - 1);
}
return null;
}
@Override
public Cell E() {
if (x > 0) {
return new CellImpl(x - 1, y);
}
return null;
}
@Override
public Cell W() {
int tcol = getXCells() - 1;
if (x < tcol) {
return new CellImpl(x + 1, y);
}
return null;
}
@Override
public Cell NW() {
int trow = getYCells() - 1, tcol = getXCells() - 1;
if (y < trow && x < tcol) {
return new CellImpl(x + 1, y + 1);
}
return null;
}
@Override
public Cell NE() {
int trow = getYCells() - 1;
if (y < trow && x > 0) {
return new CellImpl(x - 1, y + 1);
}
return null;
}
@Override
public Cell SE() {
if (y > 0 && x > 0) {
return new CellImpl(x - 1, y - 1);
}
return null;
}
@Override
public Cell SW() {
int tcol = getXCells() - 1;
if (y > 0 && x < tcol) {
return new CellImpl(x + 1, y - 1);
}
return null;
}
/*
* return all the neighboring cells that actually exist in
* the grid.
*/
@Override
public Collection getNeighbors() {
ArrayList ret = new ArrayList<>();
Cell c = N();
if (c != null)
ret.add(c);
c = S();
if (c != null)
ret.add(c);
c = W();
if (c != null)
ret.add(c);
c = E();
if (c != null)
ret.add(c);
c = NW();
if (c != null)
ret.add(c);
c = NE();
if (c != null)
ret.add(c);
c = SW();
if (c != null)
ret.add(c);
c = SE();
if (c != null)
ret.add(c);
return ret;
}
@Override
public boolean equals(Object o) {
return o instanceof CellImpl && ((CellImpl) o).x == x && ((CellImpl) o).y == y;
}
@Override
public int hashCode() {
return (x + ":" + y).hashCode();
}
@Override
public Cell move(int xOfs, int yOfs) {
int newX = x + xOfs;
int newY = y + yOfs;
if (newX >= 0 && newX < xDivs) {
if (newY >= 0 && newY < yDivs) {
// if (isActive(newX, newY)) {
return new CellImpl(newX, newY);
// }
}
}
return null;
}
@Override
public double getMinX() {
return Grid.this.getMinX() + (x * getCellWidth());
}
@Override
public double getMaxX() {
return this.getMinX() + getCellWidth();
}
@Override
public double getMinY() {
return Grid.this.getMinY() + ((yDivs - y - 1) * getCellHeight());
}
@Override
public double getMaxY() {
return this.getMinY() + getCellHeight();
}
@Override
public Integer getOffsetInGrid() {
return getOffset(x, y);
}
@Override
public double[] getCenter() {
return getCoordinates(getOffsetInGrid());
}
public Geometry getEnvelope() {
return ShapeValue.makeCell(getMinX(), getMinY(), getMaxX(), getMaxY());
}
public Geometry getGridEnvelope() {
return getBoundary();
}
public Grid getGrid() {
return Grid.this;
}
@Override
public IShape getGeometry() {
return new ShapeValue(getEnvelope(), crs);
}
}
public Grid() {
/*
* for conceptualization - make sure you use this properly
*/
}
@Override
public SpaceLocator getLocator(int x, int y) {
return new SpaceLocator(x, yDivs - y - 1);
}
public Grid(CoordinateReferenceSystem crs, double x1, // lonLowerBound
double y1, // latLowerBound
double x2, // lonUpperBound
double y2, // latUpperBound
int xDivs, int yDivs) {
// super(crs, x1, y1, x2, y2);
this.xOrigin = x1;
this.yOrigin = y1;
this.crs = crs;
this.envelope = new ReferencedEnvelope(x1, x2, y1, y2, crs);
setResolution(xDivs, yDivs);
}
public Grid(Grid gridExtent) {
super(gridExtent.getCRS(), gridExtent.getWest(), gridExtent.getSouth(), gridExtent
.getEast(), gridExtent.getNorth());
this.xOrigin = gridExtent.getEast();
this.yOrigin = gridExtent.getSouth();
this.setResolution(gridExtent.getXCells(), gridExtent.getYCells());
}
/**
* Create a grid from a shape in the CRS of the shape and using the given
* resolution for the larger extent.
*
* @param shape
* @param resolutionInMeters
* @throws KlabException
*/
public Grid(ShapeValue shape, String resolutionInMeters) throws KlabException {
super(shape);
ShapeValue bb = shape.getBoundingBox().convertToMeters();
double meters = parseResolution(resolutionInMeters);
int linearResolution = (int) (bb.getEnvelope().getWidth() / meters);
setAdjustedEnvelope(shape, linearResolution);
}
/**
* Parse a string like "1 km" and return the meters in it. Throw an exception if this
* cannot be parsed.
*
* @param string
* @return the resolution in meters
* @throws KlabValidationException
*/
public static double parseResolution(String string) throws KlabValidationException {
Pair pd = MiscUtilities.splitNumberFromString(string);
if (pd.getFirst() == null || pd.getSecond() == null)
throw new KlabValidationException("wrong resolution specification: " + string);
Unit uu = new Unit(pd.getSecond());
Unit mm = new Unit("m");
return mm.convert(pd.getFirst().doubleValue(), uu).doubleValue();
}
public Grid(ShapeValue shape, double resolutionInMeters) throws KlabException {
super(shape);
// ShapeValue sh = shape.transform(Geospace.get().getDefaultCRS());
// ReferencedEnvelope bb = sh.getBoundingBox().getEnvelope();
//
// double x1 = GeoNS.mercX(bb.getMinX());
// double x2 = GeoNS.mercX(bb.getMaxX());
//
// double width = (x2 > x1) ? (x2 - x1) : (x1 - x2);
//
// int linearResolution = (int) (width / resolutionInMeters);
setAdjustedEnvelope(shape, resolutionInMeters);
}
// public Grid(ShapeValue shape, double resolutionInMeters) throws ThinklabException {
// super(shape);
//
// ShapeValue sh = shape.transform(Geospace.get().getDefaultCRS());
// Envelope enve = sh.getEnvelope();
//
// // double[] bl = GeoNS.LatLonToUTMXY(enve.getMinY(), enve.getMinX());
// // double[] tr = GeoNS.LatLonToUTMXY(enve.getMaxY(), enve.getMaxX());
//
// double distX = GeoNS.getDistance(new Coordinate(enve.getMinY(), enve.getMaxX()), new
// Coordinate(enve.getMinY(), enve.getMinX())) * 1000;
// double distY = GeoNS.getDistance(new Coordinate(enve.getMaxY(), enve.getMinX()), new
// Coordinate(enve.getMinY(), enve.getMinX())) * 1000;
// // double distX = tr[0] - bl[0];
// // double distY = tr[1] - bl[1];
//
// int xcells = (int)Math.ceil(distX/resolutionInMeters);
// int ycells = (int)Math.ceil(distY/resolutionInMeters);
//
// /*
// * delta to add to max x,y
// */
// double dfacX = 1.0 - (xcells*resolutionInMeters)/distX;
// double dfacY = 1.0 - (ycells*resolutionInMeters)/distY;
//
// Envelope oEnv = shape.getEnvelope();
// double dx = (oEnv.getMaxX() - oEnv.getMinX()) * (dfacX/2);
// double dy = (oEnv.getMaxY() - oEnv.getMinY()) * (dfacY/2);
//
// this.shape = shape;
// this.envelope = new ReferencedEnvelope(oEnv.getMinX() - dx, oEnv.getMaxX() + dx,
// oEnv.getMinY() - dy, oEnv.getMaxY() + dy, shape.getCRS());
// setResolution(xcells, ycells);
// activationLayer = ThinklabRasterizer.createMask(shape, this);
// this.cellWidthMeters = resolutionInMeters;
// this.cellHeightMeters = resolutionInMeters;
// this.cellAreaMeters = resolutionInMeters*resolutionInMeters;
// }
public Grid transform(CoordinateReferenceSystem crs) throws KlabException {
if (shape.getCRS().equals(crs))
return this;
ShapeValue shape = getShape();
shape = shape.transform(crs);
return new Grid(shape, getXCells(), getYCells());
}
/**
* Set the envelope from shape.
* @param shape could be in any CRS.
* @param squareSize should be in meters
* @throws KlabException
*/
private void setAdjustedEnvelope(ShapeValue shape, double squareSize) throws KlabException {
int x = 0, y = 0;
double dx = 0, dy = 0;
ReferencedEnvelope env = shape.getEnvelope();
// Case one: both CRS and square size are in meters.
if (shape.getCRS().getCoordinateSystem().getAxis(0).getUnit().toString().equals("m")) {
double height = env.getHeight();
double width = env.getWidth();
x = (int) Math.ceil(width / squareSize);
y = (int) Math.ceil(height / squareSize);
dx = (x * squareSize) - width;
dy = (y * squareSize) - height;
ReferencedEnvelope envelop_ = new ReferencedEnvelope(env.getMinX() - dx, env.getMaxX()
+ dx, env.getMinY() - dy, env.getMaxY() + dy, shape.getCRS());
// After doing the calculations in the original CRS,
// shape and envelope is transformed to default.
this.shape = shape.transform(Geospace.get().getDefaultCRS());
try {
this.envelope = envelop_.transform(Geospace.get().getDefaultCRS(), true);
} catch (Exception e) {
// TODO: Shouldn't happen
e.printStackTrace();
}
this.crs = Geospace.get().getDefaultCRS();
}
// Case 2: CRS uses degrees
else {
// lets get height and width in meters
double height = Grid.haversine(env.getMinY(), env.getMinX(), env.getMaxY(), env.getMinX());
double width = Grid.haversine(env.getMinY(), env.getMinX(), env.getMinY(), env.getMaxX());
x = (int) Math.ceil(width / squareSize);
y = (int) Math.ceil(height / squareSize);
// Here I tried to adjust further dx and dy based on the size of the grid cell
// at the center of the Grid. Possibly not right either, so I left it out.
// double centralX = (env.getMaxX() + env.getMinX())/2;
// double stepX = (env.getMaxX() - env.getMinX())/x;
//
// double centralY = (env.getMaxY() + env.getMinY())/2;
// double stepY = (env.getMaxY() - env.getMinY())/x;
//
// double actualStepWidth = Grid.haversine(centralY,centralX,
// centralY,centralX+stepX);
//
// double actualStepHeight = Grid.haversine(centralY, centralX,
// centralY+stepY,centralX);
// dx = stepX * (squareSize/actualStepWidth) / 2;
// dy = stepY * (squareSize/actualStepHeight) / 2;
}
if (!this.crs.toString().equals(Geospace.get().getDefaultCRS().toString())) {
this.shape = this.shape.transform(Geospace.get().getDefaultCRS());
try {
this.envelope = this.envelope.transform(Geospace.get().getDefaultCRS(), true);
} catch (Exception e) {
// TODO: Shouldn't happen
e.printStackTrace();
}
this.crs = Geospace.get().getDefaultCRS();
}
this.setResolution(x, y);
activationLayer = ThinklabRasterizer.createMask(shape, this);
}
/**
* The haversine formula calculates great-circle distance between two points on a sphere
* from their longitudes and latitudes.
*
* From http://rosettacode.org/wiki/Haversine_formula#Java
*
* @param lat1 PointOne latitude
* @param lon1 PointOne longitude
* @param lat2 PointTwo latitude
* @param lon2 PointTwo longitude
* @return distance in meters
*/
public static double haversine(double lat1, double lon1, double lat2, double lon2) {
double R = 6372800; // in m
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
lat1 = Math.toRadians(lat1);
lat2 = Math.toRadians(lat2);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2)
* Math.cos(lat1) * Math.cos(lat2);
double c = 2 * Math.asin(Math.sqrt(a));
return R * c;
}
// /*
// * set envelope from shape's bounding box, adjusted to keep cells square when
// * measured in meters.
// */
// private void setAdjustedEnvelope(ShapeValue shape, int majorAxisResolution) throws ThinklabException {
//
// int x = 0, y = 0;
// double dx = 0, dy = 0;
// ReferencedEnvelope env = shape.getEnvelope();
//
// if (Geospace.get().squareCellsM()) {
// //
// // CoordinateReferenceSystem meters = Geospace.get().getMetersCRS();
// // if (Geospace.getCRSIdentifier(shape.getCRS(), false).equals("EPSG:4326")) {
// // meters = Geospace.get().getMetersCRS(env.getMinX() + env.getWidth() / 2,
// // env.getMinY() + env.getHeight() / 2);
// // }
// //
// // try {
// // env = env.transform(meters, true);
// // } catch (Exception e) {
// // throw new ThinklabValidationException(e);
// // }
// //
// // double width = env.getWidth();
// // double height = env.getHeight();
// //
// // if (width > height) {
// // double minorAxisResolution = Math.ceil(majorAxisResolution * height / width);
// // height = width * minorAxisResolution / majorAxisResolution;
// // x = majorAxisResolution;
// // y = (int) minorAxisResolution;
// // dy = (height - env.getHeight()) / 2.0;
// // } else {
// // double minorAxisResolution = Math.ceil(majorAxisResolution * width / height);
// // width = height * minorAxisResolution / majorAxisResolution;
// // x = (int) minorAxisResolution;
// // y = majorAxisResolution;
// // dx = (width - env.getWidth()) / 2.0;
// // }
// //
// // // System.out.println("ORIG-U: " + shape.getEnvelope());
// // // System.out.println("ORIG-M: " + env);
// //
// // this.shape = shape;
// // env = new ReferencedEnvelope(env.getMinX() - dx, env.getMaxX() + dx, env.getMinY() - dy,
// // env.getMaxY() + dy, meters);
// //
// // try {
// // this.envelope = env.transform(shape.getCRS(), true);
// // } catch (Exception e) {
// // throw new ThinklabValidationException(e);
// // }
//
// } else {
//
// double width = env.getWidth();
// double height = env.getHeight();
//
// if (width > height) {
// double minorAxisResolution = Math.ceil(majorAxisResolution * height / width);
// height = width * minorAxisResolution / majorAxisResolution;
// x = majorAxisResolution;
// y = (int) minorAxisResolution;
// dy = (height - env.getHeight()) / 2.0;
// } else {
// double minorAxisResolution = Math.ceil(majorAxisResolution * width / height);
// width = height * minorAxisResolution / majorAxisResolution;
// x = (int) minorAxisResolution;
// y = majorAxisResolution;
// dx = (width - env.getWidth()) / 2.0;
// }
//
// this.shape = shape;
// this.envelope = new ReferencedEnvelope(env.getMinX() - dx, env.getMaxX() + dx,
// env.getMinY() - dy, env.getMaxY() + dy, shape.getCRS());
// }
//
// // long xx = (long) x * (long) y;
// // if (xx > 2999999) {
// // throw new ThinklabValidationException(xx + " cells: please reduce resolution");
// // }
//
// this.setResolution(x, y);
//
// activationLayer = ThinklabRasterizer.createMask(shape, this);
//
// }
/**
* Create a grid from a shape in the CRS of the shape and using the given
* resolution for the larger extent.
*
* @param shape
* @param linearResolution
* @throws KlabException
*/
public Grid(ShapeValue shape, int linearResolution) throws KlabException {
super(shape);
setAdjustedEnvelope(shape, linearResolution);
}
/**
* Create a grid extent with a rasterized shape in the given grid. Will not
* clip the shape or anything: use ONLY when you already know the precise aspect factor for the extent
* resulting from the shape.
*
* @param shape
* @param x
* @param y
* @throws KlabException
*/
public Grid(ShapeValue shape, int x, int y) throws KlabException {
super(shape);
this.setResolution(x, y);
activationLayer = ThinklabRasterizer.createMask(shape, this);
}
/*
* TEMPORARY - does not create the activation layer; the parameter is a trick. Activation layers
* need to be small and smart - this is to avoid creating a monster for the time being.
*
* Create a grid extent with a rasterized shape in the given grid. Will not
* clip the shape or anything: use ONLY when you already know the precise aspect factor for the extent
* resulting from the shape.
*
* @param shape
* @param x
* @param y
* @throws ThinklabException
*/
@Deprecated
public Grid(ShapeValue shape, int x, int y, boolean dummy) throws KlabException {
super(shape);
this.setResolution(x, y);
}
// /**
// * Used to set the activation layer from a shape. This also create a ShapeExtent and
// * sets the lineage to it, so that our "object" can be reconstructed.
// *
// * @param extent
// * @param shape
// * @throws ThinklabException
// */
// public Grid(Grid extent, ShapeValue shape) throws ThinklabException {
// this(extent);
// activationLayer = ThinklabRasterizer.createMask(shape, this);
// }
public void setResolution(int xDivs, int yDivs) {
this.xDivs = xDivs;
this.yDivs = yDivs;
cellLength = getEnvelope().getWidth() / xDivs;
cellHeight = getEnvelope().getHeight() / yDivs;
}
@Override
public int[] getXYOffsets(int index) {
int xx = index % getXCells();
int yy = getYCells() - (index / getXCells()) - 1;
return new int[] { xx, yy };
}
public static int[] getXYCoordinates(int index, int width, int height) {
int xx = index % width;
int yy = /* height - (*/index / width/*) - 1*/;
return new int[] { xx, yy };
}
@Override
public int getOffset(int x, int y) {
return ((yDivs - y - 1) * getXCells()) + x;
}
/**
* Activate the cells that correspond to the passed shape. Note that this
* doesn't change the multiplicity.
*
* @param roi
* @throws KlabException
*/
public void createActivationLayer(ShapeValue roi) throws KlabException {
this.activationLayer = ThinklabRasterizer.createMask(roi, this);
this.shape = roi;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Grid) {
Grid ge = (Grid) obj;
/*
* TODO compare activation layers
*/
return xDivs == ge.xDivs && yDivs == ge.yDivs && envelope.equals(ge.envelope);
}
return false;
}
private void checkMeters() {
if (cellAreaMeters < -0.1) {
try {
/*
* TODO bullcrap
*/
metersCRS = Geospace.get().getMetersCRS();
if (Geospace.getCRSIdentifier(getCRS(), false).equals("EPSG:4326")) {
metersCRS = Geospace.get().getMetersCRS(envelope.getMinX()
+ envelope.getWidth() / 2, envelope.getMinY() + envelope.getHeight() / 2);
}
Envelope env = this.envelope.transform(metersCRS, true);
this.cellWidthMeters = env.getWidth() / getXCells();
this.cellHeightMeters = env.getHeight() / getYCells();
this.cellAreaMeters = this.cellWidthMeters * this.cellHeightMeters;
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
}
}
@Override
public double getCellWidth() {
return cellLength;
}
@Override
public double getCellHeight() {
return cellHeight;
}
public double getCellWidthMeters() {
checkMeters();
return this.cellWidthMeters;
}
public double getCellHeightMeters() {
checkMeters();
return this.cellHeightMeters;
}
public double getCellAreaMeters() {
checkMeters();
return this.cellAreaMeters;
}
public double getTotalAreaSquareMeters() {
double ret = getCellAreaMeters();
if (activationLayer != null) {
ret *= activationLayer.totalActiveCells();
} else {
ret *= (xDivs * yDivs);
}
return ret;
}
/**
* Return a box in standard AWT coordinates. Useful for some graphical ops and rasterization.
*
* @return box
*/
public java.awt.geom.Rectangle2D.Double getDefaultBox() {
ReferencedEnvelope env = getEnvelope();
return new java.awt.geom.Rectangle2D.Double(env.getMinX(), env.getMinY(), env.getWidth(), env
.getHeight());
}
@Override
public ShapeValue getShape() {
return (ShapeValue) (shape == null ? getFullExtentValue() : shape);
}
public java.awt.geom.Rectangle2D.Double getNormalizedBox() {
ReferencedEnvelope env = getEnvelope();
return new java.awt.geom.Rectangle2D.Double(env.getMinX(), env.getMinY(), env.getWidth(), env
.getHeight());
}
/**
* return the envelope of the cell at x,y, irrespective of the activation layer
*/
public ReferencedEnvelope getCellEnvelope(int x, int y) {
double x1 = xOrigin + (cellLength * x);
double y1 = yOrigin + (cellHeight * y);
double x2 = x1 + cellLength;
double y2 = y1 + cellHeight;
return new ReferencedEnvelope(x1, x2, y1, y2, getCRS());
}
@Override
public double getCellArea() {
return getCellAreaMeters();
// Envelope env = getCellEnvelope(0, 0);
// return env.getHeight() * env.getWidth();
}
@Override
public String toString() {
return "grid(" + xDivs + "," + yDivs + ": " + envelope.getMinX() + " : " + envelope.getMaxX() + ", "
+ envelope.getMinY() + " : " + envelope.getMaxY() + " " + getCRS().getName() + ")";
}
public IGeometricShape getFullExtentValue() {
return new ShapeValue(getBoundary(), crs);
}
public ShapeValue getCellPolygon(int index) {
Cell cell = getCell(index);
return (ShapeValue) cell.getGeometry();
// /*
// * determine coordinates of granule. This should not get called if the activation layer
// * is null, so no check for now.
// */
// Pair xy = requireActivationLayer(true).getCell(index);
//
// /*
// * TODO reimplement to use nextCell on activation layer. Must enforce sequential access
// * to use effectively.
// */
//
// double x1 = envelope.getMinX() + (cellLength * xy.getFirst());
// double y1 = envelope.getMinY() + (cellHeight * xy.getSecond());
// double x2 = x1 + cellLength;
// double y2 = y1 + cellHeight;
//
// return new ShapeValue(x1, y1, x2, y2, crs);
}
public Cell getCell(int index) {
int[] xy = getXYOffsets(index);
return new CellImpl(xy[0], xy[1]);
}
public Cell getCell(int x, int y) {
return new CellImpl(x, y);
}
@Override
public int getCellCount() {
return xDivs * yDivs;
}
public Geometry getBoundary() {
if (boundary == null) {
ReferencedEnvelope env = getEnvelope();
boundary = ShapeValue.makeCell(env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY());
}
return boundary;
}
@Override
public int getXCells() {
return xDivs;
}
@Override
public int getYCells() {
return yDivs;
}
public int getXMaxCell() {
return xDivs;
}
public int getXMinCell() {
return 0;
}
public int getYMinCell() {
return 0;
}
public int getYMaxCell() {
return yDivs;
}
public GridEnvelope getGridRange() {
return new GeneralGridEnvelope(new int[] { 0, 0 }, new int[] { xDivs, yDivs }, false);
}
public IGridMask getActivationLayer() throws KlabValidationException {
if (activationLayer == null) {
throw new KlabValidationException("no activation layer in grid extent");
}
return activationLayer;
}
public IGridMask requireActivationLayer(boolean active) {
if (activationLayer == null) {
activationLayer = new RasterActivationLayer(xDivs, yDivs, active, this);
((RasterActivationLayer)activationLayer).createMask();
}
return activationLayer;
}
public double getNSResolution() {
return cellHeight;
}
public double getEWResolution() {
return cellLength;
}
/*
* TODO check: this should be creating the largest extent that preserves
* the topology of the original extent and covers the other extent. Cell size should
* be the same as in the original extent, and the envelope should be the same or
* smaller.
*
* @see org.integratedmodelling.geospace.extents.ArealExtent#createMergedExtent(org.integratedmodelling.geospace.extents.ArealExtent, org.integratedmodelling.geospace.extents.ArealExtent, org.opengis.referencing.crs.CoordinateReferenceSystem, com.vividsolutions.jts.geom.Envelope, com.vividsolutions.jts.geom.Envelope, com.vividsolutions.jts.geom.Envelope)
*/
@Override
protected Area createMergedExtent(Area orextent, Area otextent, CoordinateReferenceSystem ccr, Envelope common, Envelope orenvnorm, Envelope otenvnorm)
throws KlabException {
/*
* for now, raster always wins
*/
if (otextent instanceof Grid && !(orextent instanceof Grid)) {
return makeRasterExtent((Grid) otextent, orextent, ccr, common);
}
if (orextent instanceof Grid && !(otextent instanceof Grid)) {
return makeRasterExtent((Grid) orextent, otextent, ccr, common);
}
/*
* if we get here, we must be merging two rasters
*/
if (!(orextent instanceof Grid && otextent instanceof Grid)) {
throw new KlabUnsupportedOperationException("RasterModel: cannot yet merge extents of different types");
}
Grid orext = (Grid) orextent;
Grid otext = (Grid) otextent;
/* we'll fix the resolution later */
Grid nwext = new Grid(ccr, common.getMinX(), common.getMinY(), common.getMaxX(), common
.getMaxY(), 1, 1);
// this is the constrained resolution; we recompute it below if we are free to choose
int xc = otext.getXCells();
int yc = otext.getYCells();
// phase errors in both directions due to choosing resolutions that do not
// match exactly. This is computed but not used right now. We could set what to
// do with it as a property: e.g., log a warning or even raise an exception if nonzero.
double errx = 0.0;
double erry = 0.0;
/* choose the smallest of the reprojected cells unless we're constrained to accept otextent */
Envelope cor = null;
Envelope cot = null;
try {
cor = orext.getCellEnvelope(0, 0).transform(ccr, true, 10);
cot = otext.getCellEnvelope(0, 0).transform(ccr, true, 10);
} catch (Exception e) {
throw new KlabValidationException(e);
}
double aor = cor.getHeight() * cor.getWidth();
double aot = cot.getHeight() * cot.getWidth();
/*
* We take the finest res
*/
Envelope cell = aor < aot ? cor : cot;
// System.out.println("cells are " + cor + " and " + cot + "; chosen " + cell + " because areas are "
// + aor + " and " + aot);
/* recompute the number of cells in the new extent */
xc = (int) Math.round(nwext.getEnvelope().getWidth() / cell.getWidth());
yc = (int) Math.round(nwext.getEnvelope().getHeight() / cell.getHeight());
errx = nwext.getEnvelope().getWidth() - (cell.getWidth() * xc);
erry = nwext.getEnvelope().getHeight() - (cell.getHeight() * yc);
// System.out.println("new cell size is " + xc + "," + yc);
// TODO use the error, make sure it's not larger than too much
// System.out.println("errors are " + errx + "," + erry);
nwext.setResolution(xc, yc);
System.out.println("extent is now " + nwext);
return nwext;
}
private Area makeRasterExtent(Grid grid, Area otextent, CoordinateReferenceSystem ccr, Envelope env)
throws KlabException {
/*
* TODO adjust the extent as necessary to make the grid extent reflect the coordinate system and area
*/
Grid ret = new Grid(ccr, env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY(), grid
.getXCells(), grid.getYCells());
return ret;
}
@Override
protected Area createConstrainedExtent(Area orextent, Area otextent, CoordinateReferenceSystem ccr, Envelope common, Envelope orenvnorm, Envelope otenvnorm)
throws KlabException {
/*
* for now, raster always wins
*/
if (otextent instanceof Grid && !(orextent instanceof Grid)) {
return makeRasterExtent((Grid) otextent, orextent, ccr, common);
}
if (orextent instanceof Grid && !(otextent instanceof Grid)) {
return makeRasterExtent((Grid) orextent, otextent, ccr, common);
}
/*
* if we get here, we must be merging two rasters
*/
if (!(orextent instanceof Grid && otextent instanceof Grid)) {
throw new KlabUnsupportedOperationException("RasterModel: cannot yet merge extents of different types");
}
Grid otext = (Grid) otextent;
double xRatio = common.getWidth() / otenvnorm.getWidth();
double yRatio = common.getHeight() / otenvnorm.getHeight();
/* recompute the number of cells in the new extent */
int xc = (int) Math.round(otext.getXCells() * xRatio);
int yc = (int) Math.round(otext.getYCells() * yRatio);
/* we'll fix the resolution later */
Grid nwext = new Grid(ccr, common.getMinX(), common.getMinY(), common.getMaxX(), common
.getMaxY(), xc, yc);
System.out.println("constrained extent is now " + nwext);
return nwext;
}
public String getSignature() {
return "grid," + getWest() + "," + getSouth() + "," + getEast() + "," + getNorth() + ","
+ getXCells() + "," + getYCells() + Geospace.getCRSIdentifier(getCRS(), false);
}
// public IExtent getAggregatedExtent() {
// return new ShapeExtent((ShapeValue) getFullExtentValue());
// }
// @Override
// public Collection> getStateLocators(int index) {
//
// Pair xy = requireActivationLayer(true).getCell(index);
//
// int brow = 0, bcol = 0, trow = getYCells() -1, tcol = getXCells() - 1;
// int col = xy.getFirst(), row = xy.getSecond();
// int n = 0;
//
// ArrayList> ret = new ArrayList>();
//
// // n
// if (row < trow) {
// n = getIndex(col, row + 1);
// ret.add(new Pair("n", n));
// }
//
// // s
// if (row > brow) {
// n = getIndex(col, row - 1);
// ret.add(new Pair("s", n));
// }
//
// // e
// if (col > bcol) {
// n = getIndex(col - 1, row);
// ret.add(new Pair("e", n));
// }
//
// // w
// if (col < tcol) {
// n = getIndex(col + 1, row);
// ret.add(new Pair("w", n));
// }
//
// // nw
// if (row < trow && col < tcol) {
// n = getIndex(col + 1, row + 1);
// ret.add(new Pair("nw", n));
// }
//
// // ne
// if (row < trow && col > bcol) {
// n = getIndex(col - 1, row + 1);
// ret.add(new Pair("ne", n));
// }
//
// // se
// if (row > brow && col > bcol) {
// n = getIndex(col - 1, row - 1);
// ret.add(new Pair("se", n));
// }
//
// // sw
// if (row > brow && col < tcol) {
// n = getIndex(col + 1, row - 1);
// ret.add(new Pair("sw", n));
// }
//
// return ret;
// }
// public void setActivationLayer(IGridMask mask) {
// this.activationLayer = mask;
// }
// public boolean checkDomainDiscontinuity() throws ThinklabException {
// return false;
// }
public Grid intersection(IExtent extent) {
/*
* compute intersection of envelopes in our CSR. Ignore axis swap finally.
*/
ReferencedEnvelope ourenv = this.getEnvelope();
ReferencedEnvelope itsenv = null;
try {
itsenv = ((Area) extent).getEnvelope().transform(crs, true, 10);
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
Envelope comenv = ourenv.intersection(itsenv);
if (comenv == null)
return null;
/*
* adjust intersection to be in phase with our cell size, ensuring it is fully contained.
*/
double startx = comenv.getMinX();
if (startx > ourenv.getMinX()) {
double delta = startx - ourenv.getMinX();
double nc = Math.floor(delta / cellLength);
startx = ourenv.getMinX() + (cellLength * nc);
}
double endx = comenv.getMaxX();
if (endx < ourenv.getMaxX()) {
double delta = ourenv.getMaxX() - endx;
double nc = Math.floor(delta / cellLength);
endx = ourenv.getMaxX() - (cellLength * nc);
}
double starty = comenv.getMinY();
if (starty > ourenv.getMinY()) {
double delta = starty - ourenv.getMinY();
double nc = Math.floor(delta / cellHeight);
starty = ourenv.getMinY() + (cellHeight * nc);
}
double endy = comenv.getMaxY();
if (endy < ourenv.getMaxY()) {
double delta = ourenv.getMaxY() - endy;
double nc = Math.floor(delta / cellHeight);
endx = ourenv.getMaxY() - (cellHeight * nc);
}
/*
* compute new grid extent resolution for intersection.
*/
int xc = (int) ((endx - startx) / cellLength);
int yc = (int) ((endy - starty) / cellHeight);
/*
* create and return new extent.
*/
return new Grid(crs, startx, starty, endx, endy, xc, yc);
}
public Collection getNeumannNeighbors(int xcell, int ycell) {
ArrayList ret = new ArrayList();
if (inRange(xcell - 1, ycell))
ret.add(getOffset(xcell - 1, ycell));
if (inRange(xcell + 1, ycell))
ret.add(getOffset(xcell + 1, ycell));
if (inRange(xcell, ycell - 1))
ret.add(getOffset(xcell, ycell - 1));
if (inRange(xcell, ycell + 1))
ret.add(getOffset(xcell, ycell + 1));
return ret;
}
public Collection getMooreNeighbors(int xcell, int ycell) {
ArrayList ret = (ArrayList) getNeumannNeighbors(xcell, ycell);
if (inRange(xcell - 1, ycell - 1))
ret.add(getOffset(xcell - 1, ycell - 1));
if (inRange(xcell + 1, ycell))
ret.add(getOffset(xcell + 1, ycell + 1));
if (inRange(xcell, ycell - 1))
ret.add(getOffset(xcell + 1, ycell - 1));
if (inRange(xcell, ycell + 1))
ret.add(getOffset(xcell - 1, ycell + 1));
return ret;
}
/**
* Get all grid cell indexes that are within n meters of the given point.
* @param xcell
* @param ycell
* @param meters
* @return index of cells within distance
*/
public Collection getCoordinatesWithinM(int xcell, int ycell, double meters) {
ArrayList ret = new ArrayList();
int xspan = (int) (meters / getCellWidthMeters());
int yspan = (int) (meters / getCellHeightMeters());
for (int x = 0; x < xspan; x++) {
for (int y = 0; y < yspan; y++) {
if (inRange(xcell - x, ycell - y))
ret.add(getOffset(xcell - x, ycell - y));
if (inRange(xcell + x, ycell - y))
ret.add(getOffset(xcell + x, ycell - y));
if (inRange(xcell - x, ycell + y))
ret.add(getOffset(xcell - x, ycell + y));
if (inRange(xcell + x, ycell + y))
ret.add(getOffset(xcell + x, ycell + y));
}
}
return ret;
}
public boolean inRange(int x, int y) {
return x >= 0 && x < getXCells() && y >= 0 && y < getYCells();
}
public boolean isCovered(int granule) {
if (activationLayer == null) {
return true;
}
return activationLayer.isActive(granule);
}
public double[] getCoordinatesAt(int x, int y) {
double x1 = getEnvelope().getMinX() + (cellLength * x);
double y1 = getEnvelope().getMinY() + (cellHeight * (getYCells() - y - 1));
return new double[] { x1 + (cellLength / 2), y1 + (cellHeight / 2) };
}
@Override
public double[] getCoordinates(int index) {
int[] xy = getXYOffsets(index);
return getCoordinatesAt(xy[0], xy[1]);
}
// @Override
// public Object adapt() {
// Map ret = new HashMap();
// ret.put("xdivs", xDivs);
// ret.put("ydivs", yDivs);
// if (shape != null) {
// ret.put("shape", shape.toString());
// }
// return ret;
// }
public int getIndex(int x, int y) {
if (xDivs != 0) {
return (y * xDivs) + x;
}
return 0;
}
public int[] getGridCoordinatesAt(double x, double y) {
int ofs = getOffsetFromWorldCoordinates(x, y);
return getXYOffsets(ofs);
}
@Override
public int getOffsetFromWorldCoordinates(double x, double y) {
if (x < getEnvelope().getMinX() || x > getEnvelope().getMaxX() || y < getEnvelope().getMinY()
|| y > getEnvelope().getMaxY())
return -1;
int xx = (int) (((x - getEnvelope().getMinX()) / (getEnvelope().getMaxX() - getEnvelope().getMinX()))
* xDivs);
int yy = (int) (((y - getEnvelope().getMinY()) / (getEnvelope().getMaxY() - getEnvelope().getMinY()))
* yDivs);
return getIndex(xx, yy);
}
@Override
public boolean isActive(int x, int y) {
return activationLayer == null ? true : activationLayer.isActive(x, y);
}
@Override
public double getMinX() {
return getWest();
}
@Override
public double getMaxX() {
return getEast();
}
@Override
public double getMinY() {
return getSouth();
}
@Override
public double getMaxY() {
return getNorth();
}
@Override
public Iterator iterator() {
return new Iterator() {
int n = 0;
@Override
public boolean hasNext() {
return n < getCellCount();
}
@Override
public Cell next() {
return getCell(n++);
}
@Override
public void remove() {
}
};
}
/**
* Return a new grid with compatible geometry to this but limited to the
* bounding box of the passed shape, which must be included in our envelope.
* Also return an array with the x and y offsets of the grid within the
* original.
*
* @param shape
* @return
* @throws KlabException
*/
public IGrid cutToShape(ShapeValue shape) throws KlabException {
Envelope genv = new Envelope(getMinX(), getMaxX(), getMinY(), getMaxY());
Envelope senv = shape.getGeometry().getEnvelope().getEnvelopeInternal();
if (!genv.covers(senv)) {
return null;
}
/*
* adjusts envelope boundaries to cover original cells exactly
*/
double gxmin = senv.getMinX();
double gxmax = senv.getMaxX();
double dx = gxmax - gxmin;
double gymin = senv.getMinY();
double gymax = senv.getMaxY();
double dy = gymax - gymin;
int nx = (int) (dx / getCellWidth());
int ny = (int) (dy / getCellHeight());
if ((nx * getCellWidth()) < dx) {
nx++;
gxmin -= (getCellWidth() / 2);
gxmax += (getCellWidth() / 2);
}
if ((ny * getCellHeight()) < dy) {
ny++;
gymin -= (getCellHeight() / 2);
gymax += (getCellHeight() / 2);
}
int xofs = (int) ((gxmin - getMinX()) / getCellWidth());
int yofs = (int) ((gymin - getMinY()) / getCellHeight());
Grid ret = new Grid(shape, nx, ny);
ret.offsetInSupergrid = new int[] {xofs, yofs};
ret.superGridId = getSignature();
ret.createActivationLayer(shape);
return ret;
}
public String getParentGridId() {
return superGridId;
}
public int[] getOffsetInParentGrid() {
return offsetInSupergrid;
}
}
| | | |