package com.revolsys.swing.map.overlay;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import javax.swing.JComponent;
import javax.swing.undo.UndoableEdit;
import com.revolsys.collection.CollectionUtil;
import com.revolsys.collection.map.Maps;
import com.revolsys.datatype.DataType;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineCap;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.segment.LineSegment;
import com.revolsys.io.BaseCloseable;
import com.revolsys.swing.Icons;
import com.revolsys.swing.listener.BaseMouseListener;
import com.revolsys.swing.listener.BaseMouseMotionListener;
import com.revolsys.swing.map.ComponentViewport2D;
import com.revolsys.swing.map.MapPanel;
import com.revolsys.swing.map.Viewport2D;
import com.revolsys.swing.map.layer.Project;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.LayerRecord;
import com.revolsys.swing.map.layer.record.renderer.GeometryStyleRenderer;
import com.revolsys.swing.map.layer.record.style.GeometryStyle;
import com.revolsys.swing.undo.SetObjectProperty;
import com.revolsys.util.Booleans;
import com.revolsys.util.Property;
import com.revolsys.util.number.Doubles;
public class AbstractOverlay extends JComponent implements PropertyChangeListener,
BaseMouseListener, BaseMouseMotionListener, MouseWheelListener, KeyListener, FocusListener {
public static final Cursor CURSOR_LINE_ADD_NODE = Icons.getCursor("cursor_line_node_add", 8, 6);
public static final Cursor CURSOR_LINE_SNAP = Icons.getCursor("cursor_line_snap", 8, 4);
public static final Cursor CURSOR_NODE_ADD = Icons.getCursor("cursor_node_add", 8, 8);
public static final Cursor CURSOR_NODE_EDIT = Icons.getCursor("cursor_node_edit", 8, 7);
public static final Cursor CURSOR_NODE_SNAP = Icons.getCursor("cursor_node_snap", 8, 7);
public static final Cursor DEFAULT_CURSOR = Cursor.getDefaultCursor();
private static final long serialVersionUID = 1L;
public static final GeometryStyle XOR_LINE_STYLE = GeometryStyle.line(new Color(0, 0, 255), 1);
private GeometryFactory geometryFactory;
private final int hotspotPixels = 6;
private MapPanel map;
private Point snapCentre;
private int snapEventX;
private int snapEventY;
private Point snapPoint;
private int snapPointIndex;
private Map<Point, Set<CloseLocation>> snapPointLocationMap = Collections.emptyMap();
private final List<Point> snapPoints = new ArrayList<>();
private ComponentViewport2D viewport;
private Geometry xorGeometry;
protected AbstractOverlay(final MapPanel map) {
this.map = map;
this.viewport = map.getViewport();
map.addMapOverlay(this);
}
protected void addOverlayAction(final String name, final Cursor cursor,
final String... overrideOverlayActions) {
if (this.map != null) {
this.map.setOverlayActionCursor(name, cursor);
this.map.addOverlayActionOverride(name, overrideOverlayActions);
}
}
public void addOverlayActionOverride(final String overlayAction,
final String... overrideOverlayActions) {
if (this.map != null) {
this.map.addOverlayActionOverride(overlayAction, overrideOverlayActions);
}
}
protected void addUndo(final UndoableEdit edit) {
this.map.addUndo(edit);
}
protected void appendLocations(final StringBuilder text, final String title,
final Map<String, Set<CloseLocation>> vertexLocations) {
if (!vertexLocations.isEmpty()) {
text.append(
"<div style=\"border-bottom: solid black 1px; font-weight:bold;padding: 1px 3px 1px 3px\">");
text.append(title);
text.append("</div>");
text.append("<div style=\"padding: 1px 3px 1px 3px\">");
for (final Entry<String, Set<CloseLocation>> entry : vertexLocations.entrySet()) {
final String typePath = entry.getKey();
final Set<CloseLocation> locations = entry.getValue();
final CloseLocation firstLocation = CollectionUtil.get(locations, 0);
final String idFieldName = firstLocation.getIdFieldName();
text.append("<b><i>");
text.append(typePath);
text.append("</i></b>\n");
text.append(
"<table cellspacing=\"0\" cellpadding=\"1\" style=\"border: solid black 1px;margin: 3px 0px 3px 0px;padding: 0px;width: 100%\">");
text.append(
"<thead><tr style=\"border-bottom: solid black 3px\"><th style=\"border-right: solid black 1px\">");
final boolean hasId = Property.hasValue(idFieldName);
if (hasId) {
text.append(idFieldName);
text.append("</th><th style=\"border-right: solid black 1px\">");
}
text.append(
"INDEX</th><th style=\"border-right: solid black 1px\">SRID</th><th>POINT</th></tr></th><tbody>");
for (final CloseLocation location : locations) {
text.append(
"<tr style=\"border-bottom: solid black 1px\"><td style=\"border-right: solid black 1px\">");
final Object id = location.getId();
if (hasId) {
text.append(id);
text.append("</td><td style=\"border-right: solid black 1px\">");
}
text.append(location.getIndexString());
text.append("</td><td style=\"border-right: solid black 1px\">");
final Point point = location.getSourcePoint();
text.append(point.getCoordinateSystemId());
text.append("</td><td>");
appendPoint(text, point);
text.append("</td></tr>");
}
text.append("</tbody></table>");
}
text.append("</div>");
}
}
protected void appendPoint(final StringBuilder text, final Point point) {
final Viewport2D viewport = getViewport();
final double viewportScale = viewport.getScale();
final double unitsPerPixel = viewport.getUnitsPerPixel();
final GeometryFactory geometryFactory = getGeometryFactory();
double scale = geometryFactory.getScaleXY();
if (geometryFactory.isProjected()) {
if (unitsPerPixel > 2) {
scale = 1.0;
}
}
final double x = point.getX();
text.append(Doubles.toString(Doubles.makePrecise(scale, x)));
text.append(",");
final double y = point.getY();
text.append(Doubles.toString(Doubles.makePrecise(scale, y)));
}
public boolean canOverrideOverlayAction(final String newAction) {
if (this.map == null) {
return false;
} else {
return this.map.canOverrideOverlayAction(newAction);
}
}
protected void clearMapCursor() {
getMap().clearToolTipText();
setMapCursor(null);
}
protected void clearMapCursor(final Cursor cursor) {
if (getMapCursor() == cursor) {
clearMapCursor();
}
}
public boolean clearOverlayAction(final String overlayAction) {
if (this.map == null) {
return false;
} else {
return this.map.clearOverlayAction(overlayAction);
}
}
protected void clearSnapLocations() {
this.snapPointLocationMap = Collections.emptyMap();
this.snapPoint = null;
}
protected void clearUndoHistory() {
final MapPanel map = getMap();
if (map != null) {
map.getUndoManager().discardAllEdits();
}
}
public void destroy() {
this.map = null;
this.snapPoint = null;
this.snapPointLocationMap.clear();
this.viewport = null;
this.xorGeometry = null;
}
protected void drawXorGeometry(final Graphics2D graphics) {
Geometry geometry = this.xorGeometry;
if (geometry != null) {
geometry = geometry.newGeometry(getViewport().getGeometryFactory2dFloating());
final Paint paint = graphics.getPaint();
try {
graphics.setXORMode(Color.WHITE);
if (geometry instanceof Point) {
final Point point = (Point)geometry;
final Point2D screenPoint = this.viewport.toViewPoint(point);
final double x = screenPoint.getX() - getHotspotPixels();
final double y = screenPoint.getY() - getHotspotPixels();
final int diameter = 2 * getHotspotPixels();
final Shape shape = new Ellipse2D.Double(x, y, diameter, diameter);
graphics.setPaint(new Color(0, 0, 255));
graphics.fill(shape);
} else {
XOR_LINE_STYLE.setLineCap(LineCap.BUTT);
try (
BaseCloseable transformCloseable = this.viewport.setUseModelCoordinates(graphics,
true)) {
GeometryStyleRenderer.renderGeometry(this.viewport, graphics, geometry, XOR_LINE_STYLE);
}
}
} finally {
graphics.setPaint(paint);
}
}
}
@Override
public final void focusGained(final FocusEvent e) {
}
@Override
public void focusLost(final FocusEvent e) {
}
protected double getDistance(final MouseEvent event) {
final int x = event.getX();
final int y = event.getY();
final GeometryFactory geometryFactory = getGeometryFactory();
final Point p1 = this.viewport.toModelPoint(x, y).convertPoint2d(geometryFactory);
final Point p2 = this.viewport.toModelPoint(x + getHotspotPixels(), y + getHotspotPixels())
.convertPoint2d(geometryFactory);
return p1.distancePoint(p2);
}
protected Point getEventPoint() {
final MouseOverlay mouseOverlay = this.map.getMouseOverlay();
return mouseOverlay.getEventPointRounded();
}
protected java.awt.Point getEventPosition() {
final MouseOverlay mouseOverlay = this.map.getMouseOverlay();
return mouseOverlay.getEventPosition();
}
protected GeometryFactory getGeometryFactory() {
GeometryFactory geometryFactory = this.geometryFactory;
if (geometryFactory == null) {
geometryFactory = getProject().getGeometryFactory();
if (geometryFactory == null) {
geometryFactory = getViewportGeometryFactory();
}
}
return geometryFactory;
}
protected GeometryFactory getGeometryFactory2d() {
GeometryFactory geometryFactory = this.geometryFactory;
if (geometryFactory == null) {
geometryFactory = getProject().getGeometryFactory();
if (geometryFactory == null) {
return getViewportGeometryFactory2d();
} else {
return geometryFactory.to2dFloating();
}
}
return geometryFactory;
}
@Override
public Graphics2D getGraphics() {
return (Graphics2D)super.getGraphics();
}
protected BoundingBox getHotspotBoundingBox() {
final Point point = MouseOverlay.getEventPoint();
final double x = point.getX();
final double y = point.getY();
final double maxDistance = this.viewport.getHotspotMapUnits();
final GeometryFactory geometryFactory = getViewportGeometryFactory2d();
return geometryFactory.newBoundingBox(x - maxDistance, y - maxDistance, x + maxDistance,
y + maxDistance);
}
public int getHotspotPixels() {
return this.hotspotPixels;
}
public MapPanel getMap() {
return this.map;
}
protected Cursor getMapCursor() {
if (this.map == null) {
return null;
} else {
return this.map.getCursor();
}
}
public String getOverlayAction() {
if (this.map == null) {
return null;
} else {
return this.map.getOverlayAction();
}
}
protected Cursor getOverlayActionCursor(final String name) {
if (this.map == null) {
return DEFAULT_CURSOR;
} else {
return this.map.getOverlayActionCursor(name);
}
}
protected Point getOverlayPoint() {
final int x = MouseOverlay.getEventX();
final int y = MouseOverlay.getEventY();
return getPoint(x, y);
}
protected Point getPoint(final GeometryFactory geometryFactory, final MouseEvent event) {
final Viewport2D viewport = getViewport();
final Point point = viewport.toModelPointRounded(geometryFactory, event.getX(), event.getY());
return point;
}
protected Point getPoint(final int x, final int y) {
final GeometryFactory geometryFactory = getGeometryFactory();
final Point point = this.viewport.toModelPointRounded(geometryFactory, x, y);
return point;
}
protected Point getPoint(final MouseEvent event) {
if (event == null) {
return null;
} else {
final int x = event.getX();
final int y = event.getY();
return getPoint(x, y);
}
}
public Project getProject() {
return this.map.getProject();
}
protected List<LayerRecord> getSelectedRecords(final BoundingBox boundingBox) {
final MapPanel map = getMap();
return map.getSelectedRecords(boundingBox);
}
protected List<AbstractRecordLayer> getSnapLayers() {
final Project project = getProject();
final MapPanel map = getMap();
final double scale = map.getScale();
return AbstractRecordLayer.getVisibleLayers(project, scale);
}
public Point getSnapPoint() {
return this.snapPoint;
}
public Map<Point, Set<CloseLocation>> getSnapPointLocationMap() {
return this.snapPointLocationMap;
}
public Viewport2D getViewport() {
return this.viewport;
}
protected GeometryFactory getViewportGeometryFactory() {
if (this.viewport == null) {
return GeometryFactory.DEFAULT_3D;
} else {
return this.viewport.getGeometryFactory();
}
}
protected GeometryFactory getViewportGeometryFactory2d() {
if (this.viewport == null) {
return GeometryFactory.DEFAULT_3D;
} else {
return this.viewport.getGeometryFactory2dFloating();
}
}
protected Point getViewportPoint(final java.awt.Point eventPoint) {
final Point point = this.viewport.toModelPoint(eventPoint);
return point;
}
public Geometry getXorGeometry() {
return this.xorGeometry;
}
public boolean hasOverlayAction() {
if (this.map == null) {
return false;
} else {
return this.map.hasOverlayAction();
}
}
protected boolean hasSnapPoint() {
this.snapPoint = null;
this.snapEventX = MouseOverlay.getEventX();
this.snapEventY = MouseOverlay.getEventY();
this.snapCentre = MouseOverlay.getEventPoint();
final double snapCentreX = this.snapCentre.getX();
final double snapCentreY = this.snapCentre.getY();
final double maxDistance = this.viewport.getHotspotMapUnits();
final BoundingBox boundingBox = this.snapCentre.getBoundingBox().expand(maxDistance);
final GeometryFactory geometryFactory = getViewportGeometryFactory2d();
final Map<Point, Set<CloseLocation>> snapLocations = new HashMap<>();
final List<AbstractRecordLayer> layers = getSnapLayers();
for (final AbstractRecordLayer layer : layers) {
final List<LayerRecord> records = layer.getRecordsBackground(boundingBox);
for (final LayerRecord record : records) {
if (layer.isVisible(record)) {
final Geometry recordGeometry = record.getGeometry();
final CloseLocation closeLocation = this.map.findCloseLocation(geometryFactory, layer,
record, recordGeometry, snapCentreX, snapCentreY, maxDistance);
if (closeLocation != null) {
final Point closePoint = closeLocation.getViewportPoint();
Maps.addToSet(snapLocations, closePoint, closeLocation);
}
}
}
}
return setSnapLocations(snapLocations);
}
public boolean isMouseInMap() {
return MouseOverlay.isMouseInMap();
}
public boolean isOverlayAction(final String overlayAction) {
if (this.map == null) {
return false;
} else {
return overlayAction.equals(this.map.getOverlayAction());
}
}
@Override
public void keyPressed(final KeyEvent e) {
}
@Override
public void keyReleased(final KeyEvent e) {
}
@Override
public void keyTyped(final KeyEvent e) {
final char keyChar = e.getKeyChar();
if (keyChar >= '0' && keyChar <= '9') {
final int snapPointIndex = keyChar - '0';
if (snapPointIndex <= getSnapPointLocationMap().size()) {
setSnapPointIndex(snapPointIndex);
setSnapLocations(getSnapPointLocationMap());
}
}
}
@Override
public void mouseEntered(final MouseEvent e) {
}
@Override
public void mouseExited(final MouseEvent e) {
}
@Override
public void mouseWheelMoved(final MouseWheelEvent e) {
}
protected void newPropertyUndo(final Object object, final String propertyName,
final Object oldValue, final Object newValue) {
final SetObjectProperty edit = new SetObjectProperty(object, propertyName, oldValue, newValue);
addUndo(edit);
}
protected LineString newXorLine(final GeometryFactory geometryFactory, final Point c0,
final Point p1) {
final Viewport2D viewport = getViewport();
final GeometryFactory viewportGeometryFactory = viewport.getGeometryFactory2dFloating();
final LineSegment line = viewportGeometryFactory.lineSegment(c0, p1);
final double length = line.getLength();
if (length > 0) {
final double cursorRadius = viewport.getModelUnitsPerViewUnit() * 6;
final Point newC1 = line.pointAlongOffset((length - cursorRadius) / length, 0);
return geometryFactory.lineString(c0, newC1);
} else {
return null;
}
}
@Override
protected final void paintComponent(final Graphics graphics) {
final Graphics2D graphics2d = (Graphics2D)graphics;
this.viewport.setGraphics(graphics2d);
try {
paintComponent(this.viewport, graphics2d);
} finally {
this.viewport.setGraphics(null);
}
}
protected void paintComponent(final Viewport2D viewport, final Graphics2D graphics) {
super.paintComponent(graphics);
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
}
protected void setGeometryFactory(final GeometryFactory geometryFactory) {
if (geometryFactory == null) {
this.geometryFactory = getViewportGeometryFactory();
} else {
this.geometryFactory = geometryFactory;
}
}
protected void setMapCursor(final Cursor cursor) {
if (this.map != null) {
this.map.setMapCursor(cursor);
}
}
public boolean setOverlayAction(final String overlayAction) {
if (this.map == null) {
return false;
} else {
final boolean set = this.map.setOverlayAction(overlayAction);
return set;
}
}
protected boolean setSnapLocations(final Map<Point, Set<CloseLocation>> snapLocations) {
if (snapLocations.isEmpty()) {
if (!this.snapPointLocationMap.isEmpty()) {
this.snapCentre = null;
this.snapPoint = null;
this.snapPointLocationMap = snapLocations;
this.snapPoints.clear();
clearMapCursor();
this.map.clearToolTipText();
}
return false;
} else {
if (!DataType.equal(snapLocations, this.snapPointLocationMap)) {
this.snapPointIndex = 1;
this.snapPointLocationMap = snapLocations;
this.snapPoints.clear();
this.snapPoints.addAll(snapLocations.keySet());
Collections.sort(this.snapPoints, new Comparator<Point>() {
@Override
public int compare(final Point point1, final Point point2) {
final Collection<CloseLocation> locations1 = snapLocations.get(point1);
final Collection<CloseLocation> locations2 = snapLocations.get(point2);
final boolean hasVertex1 = hasVertex(locations1);
final boolean hasVertex2 = hasVertex(locations2);
if (hasVertex1) {
if (!hasVertex2) {
return -1;
}
} else if (hasVertex2) {
return 0;
}
final double distance1 = AbstractOverlay.this.snapCentre.distancePoint(point1);
final double distance2 = AbstractOverlay.this.snapCentre.distancePoint(point2);
if (distance1 <= distance2) {
return -1;
} else {
return 0;
}
}
private boolean hasVertex(final Collection<CloseLocation> locations) {
for (final CloseLocation location : locations) {
if (location.getVertex() != null) {
return true;
}
}
return false;
}
});
}
final MapPanel map = getMap();
if (this.snapPointIndex == 0) {
this.snapPoint = getEventPoint();
} else {
this.snapPoint = this.snapPoints.get(this.snapPointIndex - 1);
}
boolean nodeSnap = false;
final StringBuilder text = new StringBuilder("<html>");
text.append("<div style=\"padding: 1px;");
if (0 == this.snapPointIndex) {
text.append("background-color: #0000ff;color: #ffffff");
} else {
text.append("background-color: #ffffff");
}
text.append("\"><b>0.</b> ");
final Point mousePoint = getEventPoint();
appendPoint(text, mousePoint);
text.append(" (");
text.append(mousePoint.getCoordinateSystemId());
text.append(")</div>");
int i = 1;
for (final Point snapPoint : this.snapPoints) {
text.append("<div style=\"border-top: 1px solid #666666;");
if (i == this.snapPointIndex) {
text.append("border: 2px solid #0000ff");
} else {
text.append("padding: 2px;background-color: #ffffff2");
}
text.append("\">");
text.append("<div style=\"padding: 1px;");
if (i == this.snapPointIndex) {
text.append("background-color: #0000ff;color: #ffffff");
}
text.append("\"><b>");
text.append(i);
text.append(".</b> ");
appendPoint(text, snapPoint);
text.append(" (");
text.append(snapPoint.getCoordinateSystemId());
text.append(")</div>");
final Map<String, Set<CloseLocation>> typeLocationsMap = new TreeMap<>();
for (final CloseLocation snapLocation : this.snapPointLocationMap.get(snapPoint)) {
final String typePath = snapLocation.getLayerPath();
final String locationType = snapLocation.getType();
if ("Point".equals(locationType) || "End-Vertex".equals(locationType)) {
nodeSnap = true;
}
Maps.addToSet(typeLocationsMap,
typePath + " (<b style=\"color:red\">" + locationType + "</b>)", snapLocation);
}
for (final Entry<String, Set<CloseLocation>> typeLocations : typeLocationsMap.entrySet()) {
final String type = typeLocations.getKey();
text.append("<div style=\"padding: 1px;");
if (i == this.snapPointIndex) {
text.append("background-color: #87CEFA");
} else {
// text.append("background-color: #ffffff");
}
text.append("\"> ");
text.append(type);
text.append("</div>");
}
text.append("</div>");
i++;
}
text.append("</html>");
map.setToolTipText(this.snapEventX, this.snapEventY, text);
if (Booleans.getBoolean(nodeSnap)) {
setMapCursor(CURSOR_NODE_SNAP);
} else {
setMapCursor(CURSOR_LINE_SNAP);
}
return true;
}
}
protected void setSnapPoint(final Point snapPoint) {
this.snapPoint = snapPoint;
}
public void setSnapPointIndex(final int snapPointIndex) {
this.snapPointIndex = snapPointIndex;
}
public void setXorGeometry(final Geometry xorGeometry) {
this.xorGeometry = xorGeometry;
repaint();
}
}