All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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