/*
* Copyright 2013 Serdar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.fub.agg2graphui.controller;
import de.fub.agg2graph.structs.GPSPoint;
import de.fub.agg2graph.structs.Hideable;
import de.fub.agg2graph.structs.ILocation;
import de.fub.agg2graph.structs.XYPoint;
import de.fub.agg2graph.ui.gui.RenderingOptions;
import de.fub.agg2graphui.LayerContainer;
import de.fub.agg2graphui.layers.Drawable;
import de.fub.agg2graphui.layers.LayerEvent;
import de.fub.agg2graphui.layers.LayerListener;
import de.fub.agg2graphui.layers.Line;
import de.fub.agg2graphui.layers.Point;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.openide.nodes.Node;
import org.openide.util.WeakListeners;
/**
*
* @author Serdar
* @param <T>
*/
public abstract class AbstractLayer<T> implements Hideable, PropertyChangeListener, Comparable<AbstractLayer<?>> {
protected static final Logger LOG = Logger.getLogger(AbstractLayer.class.getName());
protected final Object MUTEX = new Object();
protected final Object ITEM_LIST_MUTEX = new Object();
protected final Object DRAWABLE_LIST_MUTEX = new Object();
private int drawnPointsCounter = 0;
public static final int MAX_VISIBLE_POINTS_INTELLIGENT_LABELS = 7;
public static final int MAX_VISIBLE_POINTS_INTELLIGENT_POINT = 10;
public static final double CLIPPING_AREA_BORDER = 5000;
public static final int AGG_CONTAINER_WEIGHT_LIMIT = 2;
private RenderingOptions options;
private String name;
private String description;
private BufferedImage image;
private Rectangle2D.Double lastGpsArea;
private final List<T> itemList = new ArrayList<T>();
private final List<Drawable> drawables = new ArrayList<Drawable>();
private final Node nodeDelegate;
private final Set<LayerListener> listeners = new HashSet<LayerListener>();
private LayerManager layerManager;
private boolean visible = true;
private LayerContainer layerPanel;
public AbstractLayer(String name, RenderingOptions renderingOptions) {
this(name, "", renderingOptions);
}
public AbstractLayer(String name, String description, RenderingOptions renderingOptions) {
assert name != null;
assert description != null;
assert renderingOptions != null;
this.name = name;
this.description = description;
this.options = renderingOptions;
this.options.addPropertyChangeListener(WeakListeners.propertyChange(AbstractLayer.this, this.options));
nodeDelegate = new LayerNode(AbstractLayer.this);
}
public LayerContainer getLayerPanel() {
if (layerPanel == null) {
layerPanel = new LayerContainer(this);
}
return layerPanel;
}
@Override
public void setVisible(boolean visibility) {
if (this.visible != visibility) {
this.visible = visibility;
fireRepaintEvent();
}
}
@Override
public boolean isVisible() {
return visible;
}
public Node getNodeDelegate() {
return nodeDelegate;
}
public void add(T item) {
synchronized (ITEM_LIST_MUTEX) {
itemList.add(item);
drawables.clear();
fireRepaintEvent();
}
}
public void addAll(Collection<T> items) {
synchronized (ITEM_LIST_MUTEX) {
itemList.addAll(items);
drawables.clear();
fireRepaintEvent();
}
}
public void remove(T item) {
synchronized (ITEM_LIST_MUTEX) {
itemList.remove(item);
drawables.clear();
fireRepaintEvent();
}
}
public void clearRenderObjects() {
synchronized (ITEM_LIST_MUTEX) {
itemList.clear();
drawables.clear();
fireRepaintEvent();
}
}
public RenderingOptions getRenderingOptions() {
return options;
}
public void setOptions(RenderingOptions renderingOptions) {
this.options = renderingOptions;
}
protected List<Drawable> getDrawables() {
synchronized (DRAWABLE_LIST_MUTEX) {
return new ArrayList<Drawable>(drawables);
}
}
/**
* @return the itemList
*/
public List<T> getItemList() {
synchronized (ITEM_LIST_MUTEX) {
return new ArrayList<T>(itemList);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
/**
* @return the listeners
*/
protected Set<LayerListener> getListeners() {
return listeners;
}
/**
* @return the drawnPointsCounter
*/
protected int getDrawnPointsCounter() {
return drawnPointsCounter;
}
/**
* @param drawnPointsCounter the drawnPointsCounter to set
*/
protected void setDrawnPointsCounter(int drawnPointsCounter) {
this.drawnPointsCounter = drawnPointsCounter;
}
/**
* @return the lastGpsArea
*/
protected Rectangle2D.Double getLastGpsArea() {
return lastGpsArea;
}
/**
* @param lastGpsArea the lastGpsArea to set
*/
protected void setLastGpsArea(Rectangle2D.Double lastGpsArea) {
this.lastGpsArea = lastGpsArea;
}
/**
* @return the layerManager
*/
public LayerManager getLayerManager() {
return layerManager;
}
/**
* @param layerManager the layerManager to set
*/
protected void setLayerManager(LayerManager layerManager) {
this.layerManager = layerManager;
}
protected Polygon createArrow() {
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, (4 * 1));
arrowHead.addPoint((-4 * 1), (-8 * 1));
arrowHead.addPoint((4 * 1), (-8 * 1));
return arrowHead;
}
public Rectangle2D.Double projectRect(Rectangle2D.Double source) {
ILocation ul = new GPSPoint(source.x, source.y);
ILocation lr = new GPSPoint(source.getMaxX(), source.getMaxY());
source.x = ul.getX();
source.y = ul.getY();
source.width = lr.getX() - ul.getX();
source.height = lr.getY() - ul.getY();
return source;
}
protected void drawLine(ILocation location1, ILocation location2, RenderingOptions ro) {
drawLine(location1, location2, null, ro, 1);
}
protected void drawLine(ILocation location1, ILocation location2, RenderingOptions ro, float weightFactor) {
drawLine(location1, location2, null, ro, weightFactor);
}
protected void drawLine(ILocation location1, ILocation location2, String label, RenderingOptions ro) {
drawLine(location1, location2, label, ro, 1);
}
protected void drawLine(ILocation location1, ILocation location2, String label, RenderingOptions ro, float weightFactor) {
drawLine(location1, location2, null, ro, weightFactor, true);
}
protected void drawLine(ILocation location1, ILocation location2, String label, RenderingOptions ro, float weightFactor, boolean flag) {
if (location1 == null || location2 == null || ro.getRenderingType() == RenderingOptions.RenderingType.POINTS) {
return;
}
// make sure we only render what's visible
Point2D p1 = getLayerManager().getMapViewer().convertGeoPositionToPoint(new GeoPosition(location1.getLat(), location1.getLon()));
Point2D p2 = getLayerManager().getMapViewer().convertGeoPositionToPoint(new GeoPosition(location2.getLat(), location2.getLon()));
Line line = new Line(new XYPoint(location1.getID(), p1.getX(), p1.getY()), new XYPoint(location2.getID(), p2.getX(), p2.getY()), ro, weightFactor, flag);
line.setLabel(label);
drawables.add(line);
}
protected void drawPoint(ILocation location, RenderingOptions ro) {
if (location == null || ro.getRenderingType() == RenderingOptions.RenderingType.LINES) {
return;
}
// make sure we only render what's visible
Point2D mapPosition = getLayerManager().getMapViewer().convertGeoPositionToPoint(new GeoPosition(location.getLat(), location.getLon()));
if (mapPosition == null) {
return;
}
drawables.add(new Point(new XYPoint(location.getID(), mapPosition.getX(), mapPosition.getY()), ro));
setDrawnPointsCounter(getDrawnPointsCounter() + 1);
}
public void paintLayer(Graphics g, Rectangle rectangle) {
Graphics2D g2d = (Graphics2D) g.create();
try {
// queue draw operations
setDrawnPointsCounter(0);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
synchronized (DRAWABLE_LIST_MUTEX) {
drawables.clear();
if (isVisible()) {
drawDrawables(g2d, rectangle);
for (Drawable drawObject : getDrawables()) {
// set color
if (drawObject != null) {
RenderingOptions ro = drawObject.getRenderingOptions();
Color lineColor = new Color(
(ro.getColor().getRed() / 255f),
(ro.getColor().getGreen() / 255f),
(ro.getColor().getBlue() / 255f),
(float) ro.getOpacity());
g2d.setColor(lineColor);
if (drawObject instanceof Point) {
Point p = (Point) drawObject;
paintPoint(p, g2d);
} else if (drawObject instanceof Line) {
Line line = (Line) drawObject;
paintLine(line, g2d);
}
}
}
}
}
} finally {
g2d.dispose();
}
}
private void paintPoint(Point point, Graphics2D g2d) {
RenderingOptions renderingOptions = point.getRenderingOptions();
// draw point
if (renderingOptions.getRenderingType() == RenderingOptions.RenderingType.POINTS) {
float width = renderingOptions.getStrokeBaseWidthFactor() * RenderingOptions.getBasicStroke().getLineWidth();
g2d.fillOval((int) (point.getAt().getX() - width / 2), (int) (point.getAt().getY() - width / 2), (int) width, (int) width);
}
// draw label
// do not draw label if not fully opaque
if (renderingOptions.getOpacity() >= 1
&& point.getAt().getID() != null
&& renderingOptions.getLabelRenderingType() != RenderingOptions.LabelRenderingType.NEVER
&& (renderingOptions.getLabelRenderingType() != RenderingOptions.LabelRenderingType.INTELLIGENT
|| getDrawnPointsCounter() <= MAX_VISIBLE_POINTS_INTELLIGENT_LABELS)) {
g2d.drawString(point.getAt().getID(),
(int) point.getAt().getX(),
(int) (point.getAt().getY() + getLayerManager().getMapViewer().getHeight() * 0.075));
}
}
private void paintLine(Line line, Graphics2D g2d) {
// draw line
if (line.getRenderingOptions().getRenderingType() != RenderingOptions.RenderingType.POINTS) {
float width = line.getWeightFactor() * getLayerManager().getThicknessFactor();
Stroke stroke = line.getRenderingOptions().getStroke(width);
g2d.setStroke(stroke);
int x1 = (int) line.getFrom().getX();
int y1 = (int) line.getFrom().getY();
int x2 = (int) line.getTo().getX();
int y2 = (int) line.getTo().getY();
g2d.drawLine(x1, y1, x2, y2);
// draw start end endpoint of line
if (line.getRenderingOptions().getRenderingType() == RenderingOptions.RenderingType.ALL) {
Ellipse2D.Double ellipse = new Ellipse2D.Double(x1 - 2, y1 - 2, 4, 4);
g2d.fill(ellipse);
ellipse = new Ellipse2D.Double(x2 - 2, y2 - 2, 4, 4);
g2d.fill(ellipse);
}
// paint the line label
paintLineLabel(line, g2d);
// make a nice arrow :) (code from
// http://stackoverflow.com/a/3094933)
if ((line.getRenderingOptions().getRenderingType() != RenderingOptions.RenderingType.LINES)
&& (line.getRenderingOptions().getRenderingType() != RenderingOptions.RenderingType.INTELLIGENT_ALL
|| getDrawnPointsCounter() <= MAX_VISIBLE_POINTS_INTELLIGENT_POINT)) {
// draw arrows
if (line.isDirected()) {
paintArrows(line, g2d);
}
}
}
}
private void paintLineLabel(Line line, Graphics2D g2d) {
if (line.getLabel() != null) {
double angle = Math.atan2(line.getTo().getY() - line.getFrom().getY(), line.getTo().getX() - line.getFrom().getX());
AffineTransform oldTx = g2d.getTransform();
// rotate to vertical axis and draw string
AffineTransform tx = new AffineTransform(oldTx);
tx.translate(line.getTo().getX(), line.getTo().getY());
tx.rotate((angle - Math.PI / 2d));
g2d.setTransform(tx);
g2d.drawString(line.getLabel(), 0, 0);
g2d.setTransform(oldTx);
}
}
private void paintArrows(Line line, Graphics2D g2d) {
int zoom = getLayerManager().getMapViewer().getZoom();
float width = line.getWeightFactor() * getLayerManager().getThicknessFactor();
double angle = Math.atan2(line.getTo().getY() - line.getFrom().getY(), line.getTo().getX() - line.getFrom().getX());
AffineTransform oldTx = g2d.getTransform();// rotate to vertical axis and draw string
AffineTransform tx = new AffineTransform(oldTx);
tx.translate(line.getTo().getX(), line.getTo().getY());
tx.rotate((angle - Math.PI / 2d));
g2d.setTransform(tx);
g2d.setStroke(line.getRenderingOptions().getStroke(getLayerManager().getThicknessFactor()));
// the diraction arrows of the line
g2d.drawLine(0, 0, (int) (10 * 1.0 / zoom), (int) (-15 * 1.0 / zoom));
g2d.drawLine(0, 0, (int) (-10 * 1.0 / zoom), (int) (-15 * 1.0 / zoom));
g2d.setTransform(oldTx);
}
protected void fireRepaintEvent() {
synchronized (MUTEX) {
LayerEvent event = new LayerEvent(AbstractLayer.this);
for (LayerListener listener : getListeners()) {
listener.requestRender(event);
}
}
}
public void addLayerListener(LayerListener listener) {
synchronized (MUTEX) {
listeners.add(listener);
}
}
public void removeLayerListener(LayerListener listener) {
synchronized (MUTEX) {
listeners.remove(listener);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
fireRepaintEvent();
}
protected abstract void drawDrawables(Graphics2D graphics, Rectangle rectangle);
@Override
public int compareTo(AbstractLayer<?> layer) {
return ((Integer) getRenderingOptions().getzIndex()).compareTo(layer.getRenderingOptions().getzIndex());
}
}