/* AWE - Amanzi Wireless Explorer
* http://awe.amanzi.org
* (C) 2008-2009, AmanziTel AB
*
* This library is provided under the terms of the Eclipse Public License
* as described at http://www.eclipse.org/legal/epl-v10.html. Any use,
* reproduction or distribution of the library constitutes recipient's
* acceptance of this agreement.
*
* This library is distributed WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.amanzi.awe.render.core;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import net.refractions.udig.catalog.IGeoResource;
import net.refractions.udig.project.ILayer;
import net.refractions.udig.project.internal.render.impl.RendererImpl;
import net.refractions.udig.project.render.RenderException;
import org.amanzi.awe.catalog.neo.resource.GeoResource;
import org.amanzi.awe.catalog.neo.selection.IMapSelection;
import org.amanzi.awe.neostyle.BaseNeoStyle;
import org.amanzi.awe.render.core.coloring.IColoringInterceptor;
import org.amanzi.awe.render.core.coloring.IColoringInterceptorFactory;
import org.amanzi.awe.render.core.coloring.internal.ColoringInterceptorsCache;
import org.amanzi.neo.dto.IDataElement;
import org.amanzi.neo.models.exceptions.ModelException;
import org.amanzi.neo.models.render.IGISModel;
import org.amanzi.neo.models.render.IGISModel.ILocationElement;
import org.amanzi.neo.models.render.IRenderableModel;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
import com.google.common.collect.Iterables;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
/**
* TODO Purpose of
* <p>
* </p>
*
* @author grigoreva_a
* @since 1.0.0
*/
public abstract class AbstractRenderer extends RendererImpl {
private static final Logger LOGGER = Logger.getLogger(AbstractRenderer.class);
protected static BaseNeoStyle style;
protected IGISModel model;
private MathTransform transform_d2w;
private MathTransform transform_w2d;
private final AbstractRendererStyles commonStyle = initDefaultRendererStyle();
public static final String BLACKBOARD_NODE_LIST = "org.amanzi.awe.tool.star.StarTool.nodes";
public static final String SPACE_SEPARATOR = " ";
public static final String EQUAL_SEPARATOR = "=";
private static final int DEFAULT_DRAW_SIZE = 15;
private IMapSelection selection;
private IColoringInterceptor colorer;
private Collection<ILocationElement> locationElements;
private Collection<IDataElement> selectedElements;
protected double calculateCountScaled(final double dataScaled, final long count) {
return dataScaled * count / 2;
}
/**
* calculate average between necessary nodes count and size
*
* @param dbounds
* @param resource
* @return
*/
protected double calculateResult(final Envelope dbounds, final IGISModel resource) {
return 0d;
}
/**
* draw coordinate element(element which contain latitude and longitude properties for example
* Site in Network Model or Mp in drive Model) on map
*
* @param shape -shape of feature element
* @param destination
* @param size -current s
* @param isFill - is need to feel shape
*/
protected void drawCoordinateElement(final RenderShape shape, final Graphics2D destination, final Point point,
final IDataElement element, final boolean isFill) {
final int size = getSize();
final int x = point.x - size;
final int y = point.y - size;
final Color color = commonStyle.changeColor(getColor(element), commonStyle.getAlpha());
switch (shape) {
case ELLIPSE:
drawOval(destination, isFill, x, y, size, color);
break;
case RECTANGLE:
drawRect(destination, isFill, x, y, size, color);
break;
default:
break;
}
}
/**
* draw ellipse
*
* @param destination
* @param isFill
* @param x
* @param y
* @param size
* @param color
*/
protected void drawOval(final Graphics2D destination, final boolean isFill, final int x, final int y, final int size,
final Color color) {
destination.setColor(commonStyle.getBorderColor());
destination.drawOval(x, y, size, size);
if (isFill) {
destination.setColor(color);
destination.fillOval(x, y, size, size);
}
}
/**
* draw rectangle
*
* @param destination
* @param isFill
* @param x
* @param y
* @param size
* @param color
*/
protected void drawRect(final Graphics2D destination, final boolean isFill, final int x, final int y, final int size,
final Color color) {
if (isFill) {
destination.setColor(color);
destination.fillRect(x, y, size, size);
} else {
destination.setColor(commonStyle.getBorderColor());
destination.drawRect(x, y, size, size);
}
}
/**
* get color from distribution ... if distribution not exist then return default border color
*
* @param element
* @return
*/
protected Color getColor(final IDataElement element) {
Color result = null;
if (colorer != null) {
result = colorer.getColor(element);
}
if (result == null) {
result = getDefaultColor(element);
}
return result;
}
/**
* return default color
*
* @param element
* @return
*/
protected Color getDefaultColor(final IDataElement element) {
switch (commonStyle.getScale()) {
case MEDIUM:
case SMALL:
return commonStyle.getBorderColor();
case LARGE:
return getDefaultFillColorByElement(element);
default:
return null;
}
}
/**
* return default fill color for element;
*/
protected abstract Color getDefaultFillColorByElement(IDataElement element);
/**
* Get point
*
* @param model
* @param element
* @param bounds_transformed
* @return point or null
* @throws TransformException
*/
protected Point getPoint(final IGISModel model, final ILocationElement element, final Envelope bounds_transformed)
throws TransformException {
final Coordinate location = new Coordinate(element.getLongitude(), element.getLatitude());
java.awt.Point point = null;
if (location != null && bounds_transformed != null && bounds_transformed.contains(location)) {
final Coordinate world_location = new Coordinate();
try {
JTS.transform(location, world_location, transform_d2w);
} catch (final Exception e) {
JTS.transform(location, world_location, transform_w2d.inverse());
}
point = getContext().worldToPixel(world_location);
}
return point;
}
/**
* return renderable element count
*
* @param model2
*/
protected abstract long getRenderableElementCount(IGISModel model);
/**
* return class which can be resolved with georesource
*
* @return
*/
protected abstract Class< ? extends IRenderableModel> getResolvedClass();
/**
* return default element size depends of scale
*
* @return
*/
protected int getSize() {
switch (commonStyle.getScale()) {
case MEDIUM:
return commonStyle.getMediumElementSize() / 2;
case LARGE:
return commonStyle.getLargeElementSize() / 2;
default:
break;
}
return 1;
}
/**
* get transformed bounds
*
* @return
* @throws TransformException
*/
private Envelope getTransformedBounds() throws TransformException {
ReferencedEnvelope bounds = getRenderBounds();
if (bounds == null) {
bounds = context.getViewportModel().getBounds();
}
Envelope bounds_transformed = null;
if (bounds != null && transform_w2d != null) {
bounds_transformed = JTS.transform(bounds, transform_w2d);
}
return bounds_transformed;
}
/**
* Highlight selected items
*
* @param destination
* @param point point
*/
protected void highlightSelectedItem(final Graphics2D destination, final java.awt.Point point) {
int elementSize = getSize() * 2;
final float radius = 60;
final float[] fractions = {0.01f, 1.0f};
for (; elementSize > 0; elementSize *= 0.8) {
final Color[] colors = {commonStyle.changeColor(Color.CYAN, 40), commonStyle.changeColor(Color.WHITE, 40)};
destination.setPaint(new RadialGradientPaint(point.x - elementSize / 2, point.y - elementSize / 2, radius, fractions,
colors));
destination.fillOval((int)(point.x - elementSize * 2.25), (int)(point.y - elementSize * 2.25), 4 * elementSize,
4 * elementSize);
}
}
/**
* initialize default renderer styles;
*
* @return
*/
protected abstract AbstractRendererStyles initDefaultRendererStyle();
protected boolean isSelected(final IDataElement element, final boolean locationsOnly, final boolean elementsOnly) {
if (selection == null) {
return false;
} else {
boolean isSelected = elementsOnly ? false : getSelectedLocations().contains(element);
if (!isSelected && !locationsOnly) {
isSelected |= getSelectedElements().contains(element);
}
return isSelected;
}
}
private Collection<IDataElement> getSelectedElements() {
if (selectedElements == null) {
selectedElements = new ArrayList<IDataElement>();
Iterables.addAll(selectedElements, selection.getSelectedElements());
}
return selectedElements;
}
private Collection<ILocationElement> getSelectedLocations() {
if (locationElements == null) {
locationElements = new ArrayList<ILocationElement>();
Iterables.addAll(locationElements, selection.getSelectedLocations());
}
return locationElements;
}
@Override
public void render(final Graphics2D destination, final IProgressMonitor monitor) throws RenderException {
final ILayer layer = getContext().getLayer();
final IGeoResource resource = layer.findGeoResource(GeoResource.class);
// c+v
selection = (IMapSelection)layer.getMap().getBlackboard().get(IMapSelection.SELECTION_BLACKBOARD_PROPERTY);
selectedElements = null;
locationElements = null;
if (resource != null) {
try {
renderGeoResource(destination, resource, monitor);
} catch (final ModelException e) {
LOGGER.error("Could not render resource.", e);
}
}
}
@Override
public void render(final IProgressMonitor monitor) throws RenderException {
final Graphics2D g = getContext().getImage().createGraphics();
render(g, monitor);
}
/**
* render element based on latitude and longitude values;
*
* @param destination
* @param point
* @param element
*/
protected abstract void renderCoordinateElement(Graphics2D destination, Point point, IDataElement element);
/**
* render currentElement element (for example site or mp location element)
*
* @param destination
* @param point
* @param element
*/
protected abstract void renderElement(Graphics2D destination, Point point, ILocationElement element, IGISModel model)
throws ModelException;
/**
* render elements from current model
*
* @param destination
* @throws TransformException
* @throws AWEException
* @throws NoninvertibleTransformException
*/
private void renderElements(final Graphics2D destination, final Envelope bounds_transformed, final Envelope data_bounds,
final IProgressMonitor monitor) throws NoninvertibleTransformException, ModelException, TransformException {
for (final ILocationElement element : model.getElements(data_bounds)) {
final Point point = getPoint(model, element, bounds_transformed);
if (point != null) {
renderElement(destination, point, element, model);
} else {
continue;
}
monitor.worked(1);
// count++;
if (monitor.isCanceled()) {
break;
}
}
}
/**
* render selected georesource
*
* @param destination
* @param resource
* @param monitor
* @throws AWEException
*/
private void renderGeoResource(final Graphics2D destination, final IGeoResource resource, IProgressMonitor monitor)
throws RenderException, ModelException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
// TODO: Get size from info (???)
monitor.beginTask("render network sites and sectors: " + resource.getIdentifier(), IProgressMonitor.UNKNOWN);
try {
setStyle(destination);
// find a resource to render
model = resource.resolve(IGISModel.class, monitor);
if (!model.canRender()) {
return;
}
final IColoringInterceptorFactory colorerFactory = ColoringInterceptorsCache.getCache().getFactory(model);
if (colorerFactory != null) {
colorer = colorerFactory.createInterceptor(model);
}
if (selection != null && !selection.getModel().getAllGIS().contains(model)) {
selection = null;
}
// get rendering bounds and zoom
setCrsTransforms(resource.getInfo(null).getCRS());
final Envelope bounds_transformed = getTransformedBounds();
final Envelope data_bounds = model.getBounds();
Long count;
if (bounds_transformed == null) {
commonStyle.setScale(Scale.MEDIUM);
} else if (data_bounds != null && data_bounds.getHeight() > 0 && data_bounds.getWidth() > 0) {
count = getRenderableElementCount(model);
setScaling(bounds_transformed, data_bounds, monitor, count);
}
renderElements(destination, bounds_transformed, data_bounds, monitor);
} catch (final IOException e) {
LOGGER.error("Could not relosve resource.", e);
throw new RenderException(e);
} catch (final TransformException e) {
LOGGER.error("Could not transform bounds.", e);
throw new RenderException(e);
} catch (final FactoryException e) {
LOGGER.error("Could not set CRS transforms.", e);
throw new RenderException(e);
}
}
/**
* set transforms
*
* @param dataCrs
* @throws FactoryException
*/
private void setCrsTransforms(final CoordinateReferenceSystem dataCrs) throws FactoryException {
final boolean lenient = true; // needs to be lenient to work on uDIG 1.1
// (otherwise we get error:
// bursa wolf parameters required
final CoordinateReferenceSystem worldCrs = context.getCRS();
transform_d2w = CRS.findMathTransform(dataCrs, worldCrs, lenient);
transform_w2d = CRS.findMathTransform(worldCrs, dataCrs, lenient); // could
// use
// transform_d2w.inverse()
// also
}
/**
* set requirement to draw labels
*
* @param countScaled
*/
protected abstract void setDrawLabel(double countScaled);
/**
* prepare scaling value before elements render. Scale is response for the view of renderable
* elements
*
* @param bounds_transformed
* @param data_bounds
* @param monitor
* @param count
*/
public void setScaling(final Envelope bounds_transformed, final Envelope data_bounds, final IProgressMonitor monitor,
final long count) {
final double dataScaled = bounds_transformed.getHeight() * bounds_transformed.getWidth()
/ (data_bounds.getHeight() * data_bounds.getWidth());
final double countScaled = calculateCountScaled(dataScaled, count);
setDrawLabel(countScaled);
if (countScaled < commonStyle.getMaxElementsFull()) {
commonStyle.setScale(Scale.LARGE);
} else if (countScaled > commonStyle.getMaxElementsLite()) {
commonStyle.setScale(Scale.SMALL);
} else {
commonStyle.setScale(Scale.MEDIUM);
}
if (commonStyle.getScale().equals(Scale.LARGE) && commonStyle.isScaleSymbols()) {
int size = DEFAULT_DRAW_SIZE;
size *= Math.sqrt(commonStyle.getMaxElementsFull()) / (3 * Math.sqrt(countScaled));
size = Math.min(size, commonStyle.getMaxSymbolSize());
commonStyle.setLargeElementSize(size);
}
bounds_transformed.expandBy(0.75 * (bounds_transformed.getHeight() + bounds_transformed.getWidth()));
}
/**
* set default style to destination
*/
protected void setStyle(final Graphics2D destination) {
if (commonStyle.isAntialiazing()) {
destination.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
destination.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
}
}