
eu.agrosense.client.grid.Grid Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2008-2012 AgroSense Foundation.
*
* AgroSense is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* There are special exceptions to the terms and conditions of the GPLv3 as it is applied to
* this software, see the FLOSS License Exception
* .
*
* AgroSense 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AgroSense. If not, see .
*
* Contributors:
* Timon Veenstra - initial API and implementation and/or initial documentation
*/
package eu.agrosense.client.grid;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.WritableRenderedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import nl.cloudfarming.client.geoviewer.Geographical;
import nl.cloudfarming.client.geoviewer.Palette;
import nl.cloudfarming.client.model.AbstractEntity;
import nl.cloudfarming.client.model.AgroURI;
import nl.cloudfarming.client.model.EnterpriseIdProvider;
import nl.cloudfarming.client.model.ItemIdType;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.opengis.geometry.BoundingBox;
import org.openide.util.Exceptions;
/**
* A grid is a north/south oriented raster of cells with a fixed with/height.
* The bounds of the grid are geo referenced, ie they have coordinates. The Grid
* class is a wrapper for GridCoverage2D with some additional information about
* the grid.
*
* @author Timon Veenstra
*/
public class Grid extends AbstractEntity implements Geographical {
public static final int EMPTY_VALUE = Integer.MIN_VALUE;
/**
*
*/
private BoundingBox boundingBox;
private Geometry boundingBoxGeometry;
private Future boundingBoxFuture;
/**
*
*/
public static final String PROP_NAME = "name";
public static final String PROP_PALETTE = "palette";
public static final String PROP_GRID = "grid";
public static final String PROP_SOURCES = "sources";
/**
*
*/
private Set sources;
private String name;
private transient GridCoverage2D grid;
private Palette palette;
private int[] serializedRaster;
/**
*
*/
public static final ItemIdType ITEM_ID_TYPE = ItemIdType.GRD;
public static final String EXT = "grd";
public static final String MIME_TYPE = "application/x-agrosense-grid";
private final GridCellSize gridCellSize;
private static final Logger LOGGER = Logger.getLogger(Grid.class.getName());
public static final int BAND = 0;
public Grid(EnterpriseIdProvider eip, GridCellSize gridCellSize, Future boundingBoxFuture) {
super(eip, ItemIdType.GRD);
this.boundingBoxFuture = boundingBoxFuture;
this.gridCellSize = gridCellSize;
}
public Grid(EnterpriseIdProvider eip, GridCellSize gridCellSize, BoundingBox boundingBox) {
super(eip, ITEM_ID_TYPE);
this.gridCellSize = gridCellSize;
this.boundingBox = boundingBox;
}
/**
* copy constructor.
*
* Creates a new grid based on the provided grid.
*
* @param other
*/
public Grid(EnterpriseIdProvider eip, Grid other) {
this(other.getURI(), other.getGridCellSize(), other.boundingBox);
this.grid = other.grid;
}
/**
* copy constructor with new set of values
*
* Creates a new grid based on the provided grid.
*
* @param other
*/
public Grid(EnterpriseIdProvider eip, Grid other, int[] values) {
this(other.getURI(), other.getGridCellSize(), other.boundingBox);
this.grid = other.grid;
initializeGrid(values);
}
public void setValues(int[] values){
initializeGrid(values);
}
/**
* Get the value of palette
*
* @return the value of palette
*/
public Palette getPalette() {
return palette;
}
/**
* Set the value of palette
*
* @param palette new value of palette
*/
public void setPalette(Palette palette) {
Palette oldPalette = this.palette;
this.palette = palette;
getPropertyChangeSupport().firePropertyChange(PROP_PALETTE, oldPalette, palette);
}
public WritableRenderedImage getRenderedImage() {
assert this.grid != null && this.grid.isDataEditable() : "Either internal grid is not editable or the grid is null";
return (WritableRenderedImage) this.grid.getRenderedImage();
}
public void setRasterValue(Coordinate coordinate, int value) {
checkState();
if (!boundingBox.contains(coordinate.x, coordinate.y)) {
throw new IllegalArgumentException("Can only add raster values for coordinates (" + coordinate + ") within the bounding box (" + boundingBox + ")");
}
Point p = this.gridCellSize.getCell(boundingBox, coordinate);
// LOGGER.log(Level.FINEST, "raster value set to {0} on cell {1}", new Object[]{value, p});
WritableRenderedImage raster = getRenderedImage();
WritableRaster tile = raster.getWritableTile(p.x, p.y);
try {
tile.setSample(p.x, p.y, 0, value);
} catch (ArrayIndexOutOfBoundsException e) {
LOGGER.log(Level.WARNING, "ArrayIndexOutOfBoundsException for p.x={0} and p.y={1}", new Object[]{p.x, p.y});
}
raster.releaseWritableTile(p.x, p.y);
}
/**
* Get the value of name
*
* @return the value of name
*/
public String getName() {
return name;
}
/**
* Set the value of name
*
* @param name new value of name
*/
public void setName(String name) {
String oldName = this.name;
this.name = name;
getPropertyChangeSupport().firePropertyChange(PROP_NAME, oldName, name);
}
public GridCellSize getGridCellSize() {
return gridCellSize;
}
public BoundingBox getInternalBoundingBox() {
return boundingBox;
}
@Override
public Geometry getBoundingBox() {
checkState();
return boundingBoxGeometry;
}
// /**
// * getting the internal value raster for a grid will mark it as dirty.
// *
// * @return
// */
// public WritableRaster getRaster() {
// checkState();
// this.dirty = true;
// return raster;
// }
/**
* Get the grid. When the internal raster data is changed, the old grid is
* returned. When on the EDT, the grid will be updated on a background
* thread. Otherwise the method will wait for the result. Listen to property
* change listener to get notified.
*
* @return
*/
public GridCoverage2D getGrid() {
checkState();
return this.grid;
}
/**
* check the internal state of the Grid. When needed the grid is generated
*
*/
private void checkState() {
final boolean doCalcBoundingBox = ((boundingBox == null || boundingBoxGeometry == null || grid == null) && boundingBoxFuture != null);
assert (doCalcBoundingBox || boundingBox != null) : "Either one of the variables boundingBox, boundingBoxGeometry or raster has not been initialized";
if (doCalcBoundingBox) {
try {
boundingBox = boundingBoxFuture.get();
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
}
if (grid == null && SwingUtilities.isEventDispatchThread()) {
LOGGER.finest("pushing grid generation to worker thread, setting dummy raster");
grid = getDummyRaster();
new SwingWorker() {
@Override
protected Object doInBackground() throws Exception {
initializeGrid(null);
return null;
}
}.execute();
//NOT ON THE EDT
} else if (grid == null) {
initializeGrid(null);
}
}
/**
* create a dummy raster on the place of the grid. creates a 1x1 raster with
* a transparent value.
*
* Can be overriden by sub classes, will be called in checkstate(). Do not
* call checkstate in an implementation
*
* @return
*/
protected GridCoverage2D getDummyRaster() {
WritableRaster dummyRaster = WritableRaster.createBandedRaster(DataBuffer.TYPE_INT, 1, 1, 1, new Point(0, 0));
dummyRaster.setSample(0, 0, 0, EMPTY_VALUE);
GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
return factory.create("name", dummyRaster, boundingBox);
}
/**
* initialize the grid
*/
private void initializeGrid(int[] initValues) {
assert !SwingUtilities.isEventDispatchThread() : "initializeGrid should never be invoked on the EDT!";
LOGGER.finest("Initializing grid");
Coordinate[] coordinates = new Coordinate[5];
coordinates[0] = new Coordinate(boundingBox.getMinX(), boundingBox.getMinY());
coordinates[1] = new Coordinate(boundingBox.getMaxX(), boundingBox.getMinY());
coordinates[2] = new Coordinate(boundingBox.getMaxX(), boundingBox.getMaxY());
coordinates[3] = new Coordinate(boundingBox.getMinX(), boundingBox.getMaxY());
coordinates[4] = new Coordinate(boundingBox.getMinX(), boundingBox.getMinY());
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
LinearRing linearRing = new LinearRing(new CoordinateArraySequence(coordinates), geometryFactory);
boundingBoxGeometry = new Polygon(linearRing, null, geometryFactory);
Dimension d = gridCellSize.getDimension(boundingBox);
GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
int bands = 1;
Point location = new Point(0, 0);
WritableRaster raster = WritableRaster.createBandedRaster(DataBuffer.TYPE_INT, d.width, d.height, bands, location);
LOGGER.log(Level.FINEST, "created raster with dimension{0}", d);
// fill raster with min values
for (int iw = 0; iw < d.width; iw++) {
for (int ih = 0; ih < d.height; ih++) {
int value = (initValues == null) ? EMPTY_VALUE : initValues[ih * d.width + iw];
raster.setSample(iw, ih, BAND, value);
}
}
GridCoverage2D old = this.grid;
this.grid = factory.create(Grid.class.getName(), raster, boundingBox);
getPropertyChangeSupport().firePropertyChange(PROP_GRID, old, grid);
}
@Override
public com.vividsolutions.jts.geom.Point getCentroid() {
checkState();
return this.boundingBoxGeometry.getCentroid();
}
@Override
public GridCoverage2D getRenderObject(Envelope envelope) {
return getGrid();
}
@Override
public String getTooltipText() {
return null;
}
@Override
public String getIconLabel() {
return null;
}
@Override
public Image getIcon() {
return null;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
RenderedImage ri = this.grid.getRenderedImage();
serializedRaster = ri.getData().getSamples(ri.getMinX(), ri.getMinY(), ri.getWidth(), ri.getHeight(), BAND, (int[])null);
LOGGER.log(Level.FINEST, "Writing Grid, internal gridCoverage={0}", this.grid);
oos.defaultWriteObject();
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
initializeGrid(serializedRaster);
LOGGER.log(Level.FINEST, "Reading Grid, internal gridCoverage={0}", this.grid);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy