package com.revolsys.swing.map;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.BevelBorder;
import javax.swing.undo.UndoableEdit;
import org.jdesktop.swingx.JXBusyLabel;
import com.revolsys.awt.WebColors;
import com.revolsys.collection.map.Maps;
import com.revolsys.datatype.DataType;
import com.revolsys.geometry.cs.CoordinateSystem;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryComponent;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.GeometryFactoryProxy;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.util.BoundingBoxUtil;
import com.revolsys.io.BaseCloseable;
import com.revolsys.record.Record;
import com.revolsys.swing.action.enablecheck.EnableCheck;
import com.revolsys.swing.action.enablecheck.ObjectPropertyEnableCheck;
import com.revolsys.swing.component.BasePanel;
import com.revolsys.swing.field.ComboBox;
import com.revolsys.swing.listener.ConsumerSelectedItemListener;
import com.revolsys.swing.listener.EnableComponentListener;
import com.revolsys.swing.map.border.FullSizeLayoutManager;
import com.revolsys.swing.map.border.MapRulerBorder;
import com.revolsys.swing.map.component.MapPointerElevation;
import com.revolsys.swing.map.component.MapPointerLocation;
import com.revolsys.swing.map.component.SelectMapCoordinateSystem;
import com.revolsys.swing.map.component.SelectMapScale;
import com.revolsys.swing.map.component.SelectMapUnitsPerPixel;
import com.revolsys.swing.map.layer.BaseMapLayerGroup;
import com.revolsys.swing.map.layer.Layer;
import com.revolsys.swing.map.layer.LayerGroup;
import com.revolsys.swing.map.layer.NullLayer;
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.LayerRecordQuadTree;
import com.revolsys.swing.map.list.LayerGroupListModel;
import com.revolsys.swing.map.listener.FileDropTargetListener;
import com.revolsys.swing.map.overlay.AbstractOverlay;
import com.revolsys.swing.map.overlay.CloseLocation;
import com.revolsys.swing.map.overlay.EditGeoreferencedImageOverlay;
import com.revolsys.swing.map.overlay.EditRecordGeometryOverlay;
import com.revolsys.swing.map.overlay.LayerRendererOverlay;
import com.revolsys.swing.map.overlay.MeasureOverlay;
import com.revolsys.swing.map.overlay.MouseOverlay;
import com.revolsys.swing.map.overlay.SelectRecordsOverlay;
import com.revolsys.swing.map.overlay.ToolTipOverlay;
import com.revolsys.swing.map.overlay.ZoomOverlay;
import com.revolsys.swing.menu.BaseJPopupMenu;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.swing.parallel.SwingWorkerProgressBar;
import com.revolsys.swing.toolbar.ToolBar;
import com.revolsys.swing.undo.UndoManager;
import com.revolsys.util.CaseConverter;
import com.revolsys.util.MathUtil;
import com.revolsys.util.Pair;
import com.revolsys.util.Property;
import com.revolsys.util.number.Doubles;
import com.revolsys.value.GlobalBooleanValue;
public class MapPanel extends JPanel implements GeometryFactoryProxy, PropertyChangeListener {
public static final String MAP_PANEL = "INTERNAL_layeredPanel";
public static final List<Long> SCALES = Arrays.asList(500000000L, 250000000L, 100000000L,
50000000L, 25000000L, 10000000L, 5000000L, 2500000L, 1000000L, 500000L, 250000L, 100000L,
50000L, 25000L, 10000L, 5000L, 2500L, 1000L, 500L, 250L, 100L, 50L, 25L, 10L, 5L);
private static final long serialVersionUID = 1L;
public static MapPanel getMapPanel(final Layer layer) {
if (layer == null) {
return null;
} else {
final LayerGroup project = layer.getProject();
if (project == null) {
return null;
} else {
return project.getProperty(MAP_PANEL);
}
}
}
private ComboBox<Layer> baseMapLayerField;
private BaseMapLayerGroup baseMapLayers;
private LayerRendererOverlay baseMapOverlay;
private FileDropTargetListener fileDropListener;
private final JLayeredPane layeredPane;
private LayerRendererOverlay layerOverlay;
private BasePanel leftStatusBar = new BasePanel(new FlowLayout(FlowLayout.LEFT, 2, 2));
private MouseOverlay mouseOverlay;
private final Map<String, Cursor> overlayActionCursors = new HashMap<>();
private final LinkedList<Cursor> overlayActionCursorStack = new LinkedList<>();
private JLabel overlayActionLabel;
/** Map from an overlay action (current) to the overlay actions that can override it (new). */
private final Map<String, Set<String>> overlayActionOverrides = new HashMap<>();
private final LinkedList<String> overlayActionStack = new LinkedList<>();
private int overlayIndex = 1;
private SwingWorkerProgressBar progressBar;
private final Project project;
private BasePanel rightStatusBar = new BasePanel(new FlowLayout(FlowLayout.RIGHT, 2, 2));
private double scale = 500000000;
private List<Long> scales = new ArrayList<>();
private SelectMapCoordinateSystem selectCoordinateSystem;
private final GlobalBooleanValue settingBoundingBox = new GlobalBooleanValue(false);
private boolean settingScale;
private JPanel statusBarPanel;
private final ToolBar toolBar = new ToolBar();
private ToolTipOverlay toolTipOverlay;
private final UndoManager undoManager = new UndoManager();
private final GlobalBooleanValue updateZoomHistory = new GlobalBooleanValue(true);
private final ComponentViewport2D viewport;
private Component visibleOverlay;
private JButton zoomBookmarkButton;
private final LinkedList<BoundingBox> zoomHistory = new LinkedList<>();
private int zoomHistoryIndex = -1;
private LayerRecordQuadTree selectedRecordsIndex;
private List<LayerRecord> closeSelectedRecords = Collections.emptyList();
private List<CloseLocation> closeSelectedLocations;
private boolean initializing = true;
private final JXBusyLabel busyLabel = new JXBusyLabel(new Dimension(100, 100));
private final BasePanel layeredPanel;
public MapPanel(final Project project) {
super(new BorderLayout());
this.project = project;
this.selectedRecordsIndex = new LayerRecordQuadTree(project.getGeometryFactory());
this.baseMapLayers = project.getBaseMapLayers();
project.setProperty(MAP_PANEL, this);
this.layeredPane = new JLayeredPane();
this.layeredPane.setOpaque(true);
this.layeredPane.setBackground(Color.WHITE);
this.layeredPane.setVisible(true);
this.layeredPane.setLayout(new FullSizeLayoutManager());
this.layeredPanel = new BasePanel(new GridLayout(1, 1), this.layeredPane);
this.busyLabel.setDelay(200);
this.busyLabel.setBusy(true);
this.busyLabel.setHorizontalAlignment(SwingConstants.CENTER);
this.busyLabel.setVerticalAlignment(SwingConstants.CENTER);
this.busyLabel.setBackground(WebColors.White);
this.busyLabel.setOpaque(true);
// this.busyLabel.setBorder(BorderFactory.createLineBorder(WebColors.Black));
add(this.busyLabel, BorderLayout.CENTER);
this.viewport = new ComponentViewport2D(project, this.layeredPane);
final BoundingBox boundingBox = project.getViewBoundingBox();
if (boundingBox != null && !boundingBox.isEmpty()) {
this.zoomHistory.add(boundingBox);
this.zoomHistoryIndex = 0;
}
Property.addListener(this.viewport, this);
initScales();
this.viewport.setScales(getScales());
final MapRulerBorder border = new MapRulerBorder(this.viewport);
this.layeredPanel.setBorder(border);
this.baseMapOverlay = new LayerRendererOverlay(this);
this.baseMapOverlay.setLayer(NullLayer.INSTANCE);
this.layeredPane.add(this.baseMapOverlay, new Integer(0));
Property.addListener(this.baseMapOverlay, "layer", this);
this.layerOverlay = new LayerRendererOverlay(this, project);
this.layeredPane.add(this.layerOverlay, new Integer(1));
Property.addListener(this.baseMapLayers, this);
Property.addListener(project, this);
addMapOverlays();
newToolBar();
newStatusBar();
this.fileDropListener = new FileDropTargetListener(this);
this.undoManager.addKeyMap(this);
}
public void addBaseMap(final Layer layer) {
if (layer != null) {
this.baseMapLayers.addLayer(layer);
}
if (this.baseMapLayers.getLayerCount() == 1) {
if (layer.isVisible()) {
setBaseMapLayer(layer);
}
}
}
public void addCoordinateSystem(final CoordinateSystem coordinateSystem) {
this.selectCoordinateSystem.addCoordinateSystem(coordinateSystem);
}
public void addCoordinateSystem(final int srid) {
this.selectCoordinateSystem.addCoordinateSystem(srid);
}
public void addMapOverlay(final int zIndex, final JComponent overlay) {
this.layeredPane.add(overlay, new Integer(zIndex));
if (overlay instanceof PropertyChangeListener) {
final PropertyChangeListener listener = (PropertyChangeListener)overlay;
Property.addListener(this, listener);
Property.addListener(this.project, listener);
Property.addListener(this.baseMapLayers, listener);
}
Property.addListener(overlay, this);
}
public void addMapOverlay(final JComponent overlay) {
final int zIndex = 100 * this.overlayIndex++;
addMapOverlay(zIndex, overlay);
}
protected void addMapOverlays() {
new SelectRecordsOverlay(this);
new ZoomOverlay(this);
new EditRecordGeometryOverlay(this);
this.mouseOverlay = new MouseOverlay(this, this.layeredPane);
new EditGeoreferencedImageOverlay(this);
new MeasureOverlay(this);
this.toolTipOverlay = new ToolTipOverlay(this);
}
public void addOverlayActionOverride(final String overlayAction,
final String... overrideOverlayActions) {
for (final String overrideOverlayAction : overrideOverlayActions) {
Maps.addToSet(this.overlayActionOverrides, overlayAction, overrideOverlayAction);
}
}
private void addPointerLocation(final boolean geographics) {
final MapPointerLocation location = new MapPointerLocation(this, geographics);
this.leftStatusBar.add(location);
}
public void addUndo(final UndoableEdit edit) {
this.undoManager.addEdit(edit);
}
public void addZoomBookmark() {
final BoundingBox boundingBox = getBoundingBox();
if (!boundingBox.isEmpty()) {
final String name = JOptionPane.showInputDialog(this, "Enter bookmark name",
"Add Zoom Bookmark", JOptionPane.QUESTION_MESSAGE);
if (Property.hasValue(name)) {
final Project project = getProject();
project.addZoomBookmark(name, boundingBox);
}
}
}
public boolean canOverrideOverlayAction(final String newAction) {
final String currentAction = getOverlayAction();
if (newAction == null) {
return false;
} else if (currentAction == null) {
return true;
} else if (currentAction.equals(newAction)) {
return true;
} else {
final Set<String> overrideActions = this.overlayActionOverrides.get(currentAction);
if (overrideActions == null) {
return false;
} else {
return overrideActions.contains(newAction);
}
}
}
public boolean canOverrideOverlayAction(final String newAction, final String currentAction) {
if (currentAction == null) {
return true;
} else {
final Set<String> overrideActions = this.overlayActionOverrides.get(currentAction);
if (overrideActions == null) {
return false;
} else {
return overrideActions.contains(newAction);
}
}
}
public void clearCloseSelected() {
if (this.closeSelectedRecords != null) {
this.closeSelectedRecords.clear();
}
if (this.closeSelectedLocations != null) {
this.closeSelectedLocations.clear();
}
}
public boolean clearOverlayAction(final String overlayAction) {
if (overlayAction == null) {
return false;
} else if (isOverlayAction(overlayAction)) {
this.overlayActionCursorStack.pop();
this.overlayActionStack.pop();
if (hasOverlayAction()) {
final Cursor cursor = this.overlayActionCursorStack.peek();
setViewportCursor(cursor);
final String previousAction = this.overlayActionStack.peek();
this.overlayActionLabel.setText(CaseConverter.toCapitalizedWords(previousAction));
} else {
this.overlayActionLabel.setText("");
this.overlayActionLabel.setVisible(false);
setViewportCursor(AbstractOverlay.DEFAULT_CURSOR);
}
firePropertyChange("overlayAction", overlayAction, null);
return true;
} else {
return false;
}
}
public void clearToolTipText() {
this.toolTipOverlay.clearText();
}
public void clearVisibleOverlay(final Component overlay) {
if (this.visibleOverlay == overlay) {
try {
for (final Component component : this.layeredPane.getComponents()) {
if (component != this.mouseOverlay) {
component.setVisible(true);
}
}
} finally {
this.visibleOverlay = null;
}
}
}
public void clearZoomHistory() {
this.zoomHistory.clear();
this.zoomHistoryIndex = -1;
firePropertyChange("zoomPreviousEnabled", true, false);
firePropertyChange("zoomNextEnabled", true, false);
}
public void destroy() {
setDropTarget(null);
Property.removeAllListeners(this);
setDropTarget(null);
this.layerOverlay.dispose();
for (final Component overlay : this.layeredPane.getComponents()) {
if (overlay instanceof AbstractOverlay) {
final AbstractOverlay abstractOverlay = (AbstractOverlay)overlay;
abstractOverlay.destroy();
}
}
removeAll();
this.layeredPane.removeAll();
this.statusBarPanel.removeAll();
this.leftStatusBar = null;
this.rightStatusBar = null;
if (this.baseMapLayers != null) {
this.baseMapLayers.delete();
}
this.baseMapLayers = null;
this.baseMapOverlay = null;
this.fileDropListener = null;
this.layerOverlay = null;
this.progressBar = null;
this.project.reset();
this.toolBar.clear();
this.toolTipOverlay = null;
this.undoManager.die();
Property.removeAllListeners(this.viewport.getPropertyChangeSupport());
this.zoomBookmarkButton = null;
this.zoomHistory.clear();
}
@Override
protected void finalize() throws Throwable {
this.layerOverlay.dispose();
super.finalize();
}
public CloseLocation findCloseLocation(final AbstractRecordLayer layer, final LayerRecord record,
final Geometry geometry) {
final Point point = MouseOverlay.getEventPoint();
final double x = point.getX();
final double y = point.getY();
final double maxDistance = this.viewport.getHotspotMapUnits();
final GeometryFactory geometryFactory = this.viewport.getGeometryFactory2dFloating();
return findCloseLocation(geometryFactory, layer, record, geometry, x, y, maxDistance);
}
public CloseLocation findCloseLocation(final GeometryFactory viewportGeometryFactory2d,
final AbstractRecordLayer layer, final LayerRecord record, final Geometry geometry,
final double x, final double y, final double maxDistance) {
final Geometry convertedGeometry = geometry.convertGeometry(viewportGeometryFactory2d);
final Pair<GeometryComponent, Double> closestGeometryComponent = convertedGeometry
.findClosestGeometryComponent(x, y, maxDistance);
if (!closestGeometryComponent.isEmpty()) {
final GeometryComponent geometryComponent = closestGeometryComponent.getValue1();
if (geometryComponent instanceof Vertex) {
final Vertex convertedVertex = (Vertex)geometryComponent;
final int[] vertexId = convertedVertex.getVertexId();
// Get the vertex from the original geometry
final Vertex vertex = geometry.getVertex(vertexId);
return new CloseLocation(layer, record, vertex);
} else if (geometryComponent instanceof Segment) {
final Segment convertedSegment = (Segment)geometryComponent;
final int[] segmentId = convertedSegment.getSegmentId();
// Get the segment from the original geometry
final Segment segment = geometry.getSegment(segmentId);
final Point closePoint = convertedSegment.project(x, y);
final Point sourcePoint = geometry.convertGeometry(closePoint);
return new CloseLocation(layer, record, segment, closePoint, sourcePoint);
}
}
return null;
}
public Layer getBaseMapLayer() {
return this.baseMapOverlay.getLayer();
}
public LayerGroup getBaseMapLayers() {
return this.baseMapLayers;
}
public LayerRendererOverlay getBaseMapOverlay() {
return this.baseMapOverlay;
}
public BoundingBox getBoundingBox() {
return this.viewport.getBoundingBox();
}
public List<CloseLocation> getCloseSelectedLocations() {
return this.closeSelectedLocations;
}
public List<LayerRecord> getCloseSelectedRecords() {
return this.closeSelectedRecords;
}
public FileDropTargetListener getFileDropListener() {
return this.fileDropListener;
}
@Override
public GeometryFactory getGeometryFactory() {
return this.viewport.getGeometryFactory();
}
public LayerRendererOverlay getLayerOverlay() {
return this.layerOverlay;
}
public JPanel getLeftStatusBar() {
return this.leftStatusBar;
}
@SuppressWarnings("unchecked")
public <T extends JComponent> T getMapOverlay(final Class<T> overlayClass) {
for (final Component component : this.layeredPane.getComponents()) {
if (overlayClass.isAssignableFrom(component.getClass())) {
return (T)component;
}
}
return null;
}
@SuppressWarnings("unchecked")
public <T extends JComponent> List<T> getMapOverlays(final Class<T> overlayClass) {
final List<T> overlays = new ArrayList<>();
for (final Component component : this.layeredPane.getComponents()) {
if (overlayClass.isAssignableFrom(component.getClass())) {
overlays.add((T)component);
}
}
return overlays;
}
public MouseOverlay getMouseOverlay() {
return this.mouseOverlay;
}
public String getOverlayAction() {
if (this.overlayActionStack == null) {
return null;
} else {
return this.overlayActionStack.peek();
}
}
public Cursor getOverlayActionCursor(final String name) {
return Maps.get(this.overlayActionCursors, name, AbstractOverlay.DEFAULT_CURSOR);
}
public SwingWorkerProgressBar getProgressBar() {
return this.progressBar;
}
public Project getProject() {
return this.project;
}
public JPanel getRightStatusBar() {
return this.rightStatusBar;
}
public double getScale() {
return this.scale;
}
public List<Long> getScales() {
return this.scales;
}
public List<LayerRecord> getSelectedRecords(final BoundingBox boundingBox) {
return this.selectedRecordsIndex.getItems(boundingBox);
}
public ToolBar getToolBar() {
return this.toolBar;
}
public UndoManager getUndoManager() {
return this.undoManager;
}
public double getUnitsPerPixel() {
if (this.viewport == null) {
return 1;
} else {
return this.viewport.getUnitsPerPixel();
}
}
public ComponentViewport2D getViewport() {
return this.viewport;
}
public long getZoomInScale(final double scale, final int steps) {
final long scaleCeil = (long)Math.floor(scale);
for (int i = 0; i < this.scales.size(); i++) {
long nextScale = this.scales.get(i);
if (nextScale < scaleCeil) {
for (int j = 1; j < steps && i + j < this.scales.size(); j++) {
nextScale = this.scales.get(i + j);
}
return nextScale;
}
}
return this.scales.get(this.scales.size() - 1);
}
public long getZoomOutScale(final double scale, final int steps) {
final long scaleCeil = (long)Math.floor(scale);
for (int i = this.scales.size() - 1; i >= 0; i--) {
long nextScale = this.scales.get(i);
if (nextScale > scaleCeil) {
for (int j = 1; j < steps && i - j >= 0; j++) {
nextScale = this.scales.get(i - j);
}
return nextScale;
}
}
return this.scales.get(0);
}
public boolean hasOverlayAction() {
return !this.overlayActionStack.isEmpty();
}
public boolean hasOverlayAction(final String overlayAction) {
return this.overlayActionStack.contains(overlayAction);
}
public void initScales() {
// double multiplier = 0.001;
// for (int i = 0; i < 9; i++) {
// addScale(1 * multiplier);
// addScale(2 * multiplier);
// addScale(5 * multiplier);
// multiplier *= 10;
// }
// Collections.reverse(this.scales);
this.scales = SCALES;
}
public boolean isInitializing() {
return this.initializing;
}
public boolean isOverlayAction(final String overlayAction) {
if (overlayAction == null) {
return false;
} else {
final String oldAction = getOverlayAction();
if (oldAction == null) {
return false;
} else {
return overlayAction.equals(oldAction);
}
}
}
public boolean isZoomNextEnabled() {
return this.zoomHistoryIndex < this.zoomHistory.size() - 1;
}
public boolean isZoomPreviousEnabled() {
return this.zoomHistoryIndex > 0;
}
public void mouseExitedCloseSelected(final MouseEvent event) {
this.closeSelectedRecords.clear();
}
public boolean mouseMovedCloseSelected(final MouseEvent event) {
if (isOverlayAction(SelectRecordsOverlay.ACTION_SELECT_RECORDS)
|| isOverlayAction(ZoomOverlay.ACTION_ZOOM_BOX) || isOverlayAction(ZoomOverlay.ACTION_PAN)) {
clearCloseSelected();
return false;
} else {
final double scale = getViewport().getScale();
final Point point = MouseOverlay.getEventPoint();
final double x = point.getX();
final double y = point.getY();
final double maxDistance = this.viewport.getHotspotMapUnits();
final GeometryFactory geometryFactory = this.viewport.getGeometryFactory2dFloating();
final BoundingBox boundingBox = point.newBoundingBox().expand(maxDistance);
final List<LayerRecord> closeRecords = new ArrayList<>();
final List<CloseLocation> closeLocations = new ArrayList<>();
for (final LayerRecord closeRecord : getSelectedRecords(boundingBox)) {
final AbstractRecordLayer layer = closeRecord.getLayer();
if (layer.isVisible(scale) && layer.isVisible(closeRecord)) {
final Geometry geometry = closeRecord.getGeometry();
final CloseLocation closeLocation = findCloseLocation(geometryFactory, layer, closeRecord,
geometry, x, y, maxDistance);
if (closeLocation != null) {
closeRecords.add(closeRecord);
closeLocations.add(closeLocation);
}
}
}
this.closeSelectedRecords = closeRecords;
this.closeSelectedLocations = closeLocations;
repaint();
return true;
}
}
public void moveToFront(final JComponent overlay) {
this.layeredPane.moveToFront(overlay);
}
protected void newStatusBar() {
this.statusBarPanel = new JPanel(new BorderLayout());
this.statusBarPanel.setPreferredSize(new Dimension(200, 30));
add(this.statusBarPanel, BorderLayout.SOUTH);
this.statusBarPanel.add(this.leftStatusBar, BorderLayout.WEST);
this.statusBarPanel.add(this.rightStatusBar, BorderLayout.EAST);
addPointerLocation(false);
addPointerLocation(true);
final MapPointerElevation elevation = new MapPointerElevation(this);
this.leftStatusBar.add(elevation);
this.overlayActionLabel = new JLabel();
this.overlayActionLabel.setBorder(
BorderFactory.createCompoundBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED),
BorderFactory.createEmptyBorder(2, 3, 2, 3)));
this.overlayActionLabel.setVisible(false);
this.overlayActionLabel.setForeground(WebColors.Green);
this.leftStatusBar.add(this.overlayActionLabel);
this.progressBar = new SwingWorkerProgressBar();
this.rightStatusBar.add(this.progressBar);
}
protected void newToolBar() {
add(this.toolBar, BorderLayout.NORTH);
newToolBarZoomButtons();
newToolBarUndoButtons();
newToolBarLayerControls();
}
private void newToolBarLayerControls() {
this.toolBar.addButtonTitleIcon("layers", "Refresh All Layers", "arrow_refresh", this::refresh);
this.selectCoordinateSystem = new SelectMapCoordinateSystem(this);
this.toolBar.addComponent("layers", this.selectCoordinateSystem);
final LayerGroupListModel baseMapLayersModel = new LayerGroupListModel(this.baseMapLayers,
true);
this.baseMapLayerField = baseMapLayersModel.newComboBox("baseMapLayer");
this.baseMapLayerField.setMaximumSize(new Dimension(200, 22));
ConsumerSelectedItemListener.addItemListener(this.baseMapLayerField, this::setBaseMapLayer);
if (this.baseMapLayers.getLayerCount() > 0) {
this.baseMapLayerField.setSelectedIndex(1);
}
this.baseMapLayerField.setToolTipText("Base Map");
this.toolBar.addComponent("layers", this.baseMapLayerField);
Property.addListener(this.baseMapOverlay, "layer", this);
this.baseMapLayerField.setSelectedIndex(0);
this.toolBar.addButtonTitleIcon("layers", "Refresh Base Map", "map_refresh",
this.baseMapOverlay::refresh);
}
private void newToolBarUndoButtons() {
final EnableCheck canUndo = new ObjectPropertyEnableCheck(this.undoManager, "canUndo");
final EnableCheck canRedo = new ObjectPropertyEnableCheck(this.undoManager, "canRedo");
this.toolBar.addButton("undo", "Undo", "arrow_undo", canUndo, this.undoManager::undo);
this.toolBar.addButton("undo", "Redo", "arrow_redo", canRedo, this.undoManager::redo);
}
private void newToolBarZoomButtons() {
this.toolBar.addButtonTitleIcon("zoom", "Zoom to World", "magnifier_zoom_world",
this::zoomToWorld);
this.toolBar.addButtonTitleIcon("zoom", "Zoom In", "magnifier_zoom_in", this::zoomIn);
this.toolBar.addButtonTitleIcon("zoom", "Zoom Out", "magnifier_zoom_out", this::zoomOut);
final JButton zoomPreviousButton = this.toolBar.addButtonTitleIcon("zoom", "Zoom Previous",
"magnifier_zoom_left", this::zoomPrevious);
zoomPreviousButton.setEnabled(false);
Property.addListener(this, "zoomPreviousEnabled",
new EnableComponentListener(zoomPreviousButton));
final JButton zoomNextButton = this.toolBar.addButtonTitleIcon("zoom", "Zoom Next",
"magnifier_zoom_right", this::zoomNext);
zoomNextButton.setEnabled(false);
Property.addListener(this, "zoomNextEnabled", new EnableComponentListener(zoomNextButton));
final JButton zoomSelectedButton = this.toolBar.addButtonTitleIcon("zoom", "Zoom To Selected",
"magnifier_zoom_selected", this::zoomToSelected);
zoomSelectedButton.setEnabled(false);
Property.addListener(this.project, "hasSelectedRecords",
new EnableComponentListener(zoomSelectedButton));
this.zoomBookmarkButton = this.toolBar.addButtonTitleIcon("zoom", "Zoom Bookmarks",
"zoom_bookmark", this::showZoomBookmarkMenu);
this.toolBar.addComponent("zoom", new SelectMapScale(this));
this.toolBar.addComponent("zoom", new SelectMapUnitsPerPixel(this));
}
public void panToBoundingBox(BoundingBox boundingBox) {
final GeometryFactory geometryFactory = getGeometryFactory();
boundingBox = boundingBox.convert(geometryFactory);
final Viewport2D viewport = getViewport();
if (!BoundingBoxUtil.isEmpty(boundingBox)) {
final Point centre = boundingBox.getCentre();
viewport.setCentre(centre);
}
}
public void panToGeometry(final Geometry geometry) {
if (geometry != null) {
final GeometryFactory geometryFactory = getGeometryFactory();
final Geometry convertedGeometry = geometry.convertGeometry(geometryFactory);
final BoundingBox boudingBox = convertedGeometry.getBoundingBox();
panToBoundingBox(boudingBox);
}
}
public void panToRecord(final Record record) {
if (record != null) {
final Geometry geometry = record.getGeometry();
panToGeometry(geometry);
}
}
@SuppressWarnings("unchecked")
@Override
public void propertyChange(final PropertyChangeEvent event) {
final Object source = event.getSource();
final String propertyName = event.getPropertyName();
if (AbstractRecordLayer.RECORDS_SELECTED.equals(propertyName)) {
final List<LayerRecord> oldRecords = (List<LayerRecord>)event.getOldValue();
this.selectedRecordsIndex.removeRecords(oldRecords);
final List<LayerRecord> newRecords = (List<LayerRecord>)event.getNewValue();
this.selectedRecordsIndex.addRecords(newRecords);
} else if (source == this.project) {
if ("viewBoundingBox".equals(propertyName)) {
final BoundingBox boundingBox = (BoundingBox)event.getNewValue();
setBoundingBox(boundingBox);
} else if ("geometryFactory".equals(propertyName)) {
final GeometryFactory geometryFactory = (GeometryFactory)event.getNewValue();
setGeometryFactory(geometryFactory);
}
} else if (source == this.viewport) {
if ("geometryFactory".equals(propertyName)) {
final GeometryFactory geometryFactory = this.viewport.getGeometryFactory();
setGeometryFactory(geometryFactory);
} else if ("boundingBox".equals(propertyName)) {
final BoundingBox boundingBox = this.viewport.getBoundingBox();
setBoundingBox(boundingBox);
} else if ("scale".equals(propertyName)) {
final double scale = this.viewport.getScale();
setScale(scale);
}
} else if (source == this.baseMapOverlay) {
if ("layer".equals(propertyName)) {
final Layer layer = (Layer)event.getNewValue();
if (this.baseMapLayerField != null) {
if (layer == null) {
this.baseMapLayerField.setSelectedItem(0);
} else {
this.baseMapLayerField.setSelectedItem(layer);
}
}
}
} else if (source == this.baseMapLayers) {
if ("layers".equals(propertyName)) {
if (this.baseMapOverlay != null) {
final Layer selectedLayer = this.baseMapOverlay.getLayer();
final Layer newLayer = (Layer)event.getNewValue();
if (selectedLayer != newLayer) {
if (selectedLayer != null) {
final LayerGroup selectedLayerParent = selectedLayer.getLayerGroup();
if (selectedLayer.isDeleted()
|| selectedLayerParent != null && selectedLayerParent != this.baseMapLayers) {
this.baseMapOverlay.setLayer(null);
}
}
if (newLayer != null && newLayer.isVisible()) {
this.baseMapOverlay.setLayer(newLayer);
this.baseMapLayerField.setSelectedItem(newLayer);
if (selectedLayer != null) {
selectedLayer.setVisible(false);
}
}
}
}
}
} else if (source instanceof Layer) {
final Layer layer = (Layer)source;
if (layer.getParent() == this.baseMapLayers) {
if ("visible".equals(propertyName)) {
final boolean visible = layer.isVisible();
if (visible) {
this.baseMapLayerField.setSelectedItem(layer);
} else if (!this.baseMapLayers.isHasVisibleLayer()) {
this.baseMapLayerField.setSelectedIndex(0);
}
}
}
} else if (source instanceof LayerRecord) {
final LayerRecord record = (LayerRecord)source;
if (propertyName.equals(record.getGeometryFieldName())) {
if (record.isSelected()) {
final Geometry oldValue = (Geometry)event.getOldValue();
if (oldValue == null) {
final BoundingBox boundingBox = record.getGeometry().getBoundingBox();
this.selectedRecordsIndex.removeItem(boundingBox, record);
} else {
final BoundingBox boundingBox = oldValue.getBoundingBox();
this.selectedRecordsIndex.removeItem(boundingBox, record);
}
this.selectedRecordsIndex.addRecord(record);
}
}
}
repaint();
}
public void refresh() {
final Project project = getProject();
if (project != null) {
project.refresh();
}
}
public synchronized void setBaseMapLayer(final Layer layer) {
if (layer == NullLayer.INSTANCE || this.baseMapLayers.containsLayer(layer)) {
final Layer oldValue = getBaseMapLayer();
this.baseMapOverlay.setLayer(layer);
firePropertyChange("baseMapLayer", oldValue, layer);
}
}
public synchronized void setBoundingBox(final BoundingBox boundingBox) {
Invoke.later(() -> {
if (this.settingBoundingBox.isFalse()) {
try (
BaseCloseable settingBoundingBox = this.settingBoundingBox.closeable(true)) {
final BoundingBox oldBoundingBox = getBoundingBox();
final double oldUnitsPerPixel = getUnitsPerPixel();
final boolean zoomPreviousEnabled = isZoomPreviousEnabled();
final boolean zoomNextEnabled = isZoomNextEnabled();
final BoundingBox resizedBoundingBox = this.viewport.setBoundingBox(boundingBox);
if (this.project != null) {
this.project.setViewBoundingBox(resizedBoundingBox);
setScale(this.viewport.getScale());
synchronized (this.zoomHistory) {
if (this.updateZoomHistory.isTrue() && !this.viewport.isComponentResizing()) {
BoundingBox currentBoundingBox = null;
if (this.zoomHistoryIndex > -1) {
currentBoundingBox = this.zoomHistory.get(this.zoomHistoryIndex);
if (!currentBoundingBox.equals(resizedBoundingBox)) {
while (this.zoomHistory.size() > this.zoomHistoryIndex + 1) {
this.zoomHistory.removeLast();
}
for (int i = this.zoomHistory.size() - 1; i > this.zoomHistoryIndex; i++) {
this.zoomHistory.remove(i);
}
this.zoomHistory.add(resizedBoundingBox);
this.zoomHistoryIndex = this.zoomHistory.size() - 1;
if (this.zoomHistory.size() > 50) {
this.zoomHistory.removeFirst();
this.zoomHistoryIndex--;
}
}
} else {
this.zoomHistory.add(resizedBoundingBox);
this.zoomHistoryIndex = 0;
}
}
}
firePropertyChange("unitsPerPixel", oldUnitsPerPixel, getUnitsPerPixel());
firePropertyChange("boundingBox", oldBoundingBox, resizedBoundingBox);
firePropertyChange("zoomPreviousEnabled", zoomPreviousEnabled, isZoomPreviousEnabled());
firePropertyChange("zoomNextEnabled", zoomNextEnabled, isZoomNextEnabled());
repaint();
}
}
}
});
}
public void setGeometryFactory(final GeometryFactory geometryFactory) {
if (!isSameCoordinateSystem(geometryFactory)) {
final LayerRecordQuadTree selectedRecordsIndex = new LayerRecordQuadTree(geometryFactory);
AbstractRecordLayer.forEachSelectedRecords(this.project, selectedRecordsIndex::addRecords);
this.selectedRecordsIndex = selectedRecordsIndex;
}
this.project.setGeometryFactory(geometryFactory);
this.viewport.setGeometryFactory(geometryFactory);
repaint();
}
public void setInitializing(final boolean initializing) {
Invoke.later(() -> {
if (initializing != this.initializing) {
this.initializing = initializing;
if (initializing) {
this.busyLabel.setDelay(200);
remove(this.layeredPanel);
add(this.busyLabel, BorderLayout.CENTER);
} else {
remove(this.busyLabel);
add(this.layeredPanel, BorderLayout.CENTER);
}
}
});
}
public void setMapCursor(Cursor cursor) {
if (cursor == null) {
final String overlayAction = getOverlayAction();
cursor = getOverlayActionCursor(overlayAction);
if (cursor == null) {
cursor = AbstractOverlay.DEFAULT_CURSOR;
}
}
setViewportCursor(cursor);
if (!this.overlayActionCursorStack.isEmpty()) {
this.overlayActionCursorStack.set(0, cursor);
}
}
public void setMapOverlayEnabled(final Class<? extends JComponent> overlayClass,
final boolean enabled) {
final JComponent component = getMapOverlay(overlayClass);
if (component != null) {
component.setEnabled(enabled);
}
}
public boolean setOverlayAction(final String overlayAction) {
final String oldAction = getOverlayAction();
if (overlayAction == null) {
firePropertyChange("overlayAction", oldAction, null);
return false;
} else if (DataType.equal(oldAction, overlayAction)) {
return true;
} else if (canOverrideOverlayAction(overlayAction, oldAction)) {
final Cursor cursor = getOverlayActionCursor(overlayAction);
this.overlayActionCursorStack.push(cursor);
setViewportCursor(cursor);
this.overlayActionStack.push(overlayAction);
this.overlayActionLabel.setText(CaseConverter.toCapitalizedWords(overlayAction));
this.overlayActionLabel.setVisible(true);
firePropertyChange("overlayAction", oldAction, overlayAction);
return true;
} else {
return false;
}
}
public void setOverlayActionCursor(final String name, final Cursor cursor) {
this.overlayActionCursors.put(name, cursor);
}
public synchronized void setScale(double scale) {
if (!this.settingScale && !Double.isNaN(scale) && !Double.isInfinite(scale)) {
try {
this.settingScale = true;
if (!getGeometryFactory().isGeographics()) {
scale = Doubles.makePrecise(10.0, scale);
}
if (scale >= 0.1) {
final double oldValue = this.scale;
final double oldUnitsPerPixel = getUnitsPerPixel();
if (scale != oldValue) {
this.viewport.setScale(scale);
this.scale = scale;
firePropertyChange("scale", oldValue, scale);
final double unitsPerPixel = getUnitsPerPixel();
if (Math.abs(unitsPerPixel - oldUnitsPerPixel) > 0.0001) {
firePropertyChange("unitsPerPixel", oldUnitsPerPixel, unitsPerPixel);
}
repaint();
}
}
} finally {
this.settingScale = false;
}
}
}
public void setToolTipText(final int x, final int y, final CharSequence text) {
Invoke.later(() -> {
this.toolTipOverlay.setText(x, y, text);
});
}
public void setToolTipText(final Point2D location, final CharSequence text) {
Invoke.later(() -> {
this.toolTipOverlay.setText(location, text);
});
}
public void setUnitsPerPixel(final double unitsPerPixel) {
if (this.viewport != null) {
double scale = this.viewport.getScaleForUnitsPerPixel(unitsPerPixel);
scale = Doubles.makePrecise(10.0, scale);
final double oldUnitsPerPixel = getUnitsPerPixel();
if (!MathUtil.precisionEqual(unitsPerPixel, oldUnitsPerPixel, 10000000.0)) {
setScale(scale);
}
}
}
private void setViewportCursor(final Cursor cursor) {
this.layeredPane.setCursor(cursor);
}
public void setVisibleOverlay(final JComponent overlay) {
if (this.visibleOverlay == null) {
this.visibleOverlay = overlay;
for (final Component component : this.layeredPane.getComponents()) {
if (component != overlay && component != this.mouseOverlay) {
component.setVisible(false);
}
}
}
}
private void setZoomHistoryIndex(int zoomHistoryIndex) {
synchronized (this.zoomHistory) {
try (
BaseCloseable updateZoomHistory = this.updateZoomHistory.closeable(false)) {
final boolean zoomPreviousEnabled = isZoomPreviousEnabled();
final boolean zoomNextEnabled = isZoomNextEnabled();
final int zoomHistorySize = this.zoomHistory.size();
if (zoomHistoryIndex < 1) {
zoomHistoryIndex = 0;
} else if (zoomHistoryIndex >= zoomHistorySize) {
zoomHistoryIndex = zoomHistorySize - 2;
}
this.zoomHistoryIndex = zoomHistoryIndex;
final BoundingBox boundingBox = this.zoomHistory.get(zoomHistoryIndex);
this.viewport.setBoundingBoxAndGeometryFactory(boundingBox);
this.project.setViewBoundingBoxAndGeometryFactory(boundingBox);
firePropertyChange("zoomPreviousEnabled", zoomPreviousEnabled, isZoomPreviousEnabled());
firePropertyChange("zoomNextEnabled", zoomNextEnabled, isZoomNextEnabled());
}
}
}
public void showZoomBookmarkMenu() {
final BaseJPopupMenu menu = new BaseJPopupMenu();
menu.addMenuItem("Add Bookmark", "add", this::addZoomBookmark);
menu.addSeparator();
final Project project = getProject();
for (final Entry<String, BoundingBox> entry : project.getZoomBookmarks().entrySet()) {
final String name = entry.getKey();
final BoundingBox boundingBox = entry.getValue();
menu.addMenuItem("Zoom to " + name, "magnifier", () -> zoomToBoundingBox(boundingBox));
}
menu.showMenu(this.zoomBookmarkButton, 0, 20);
}
public void toggleMode(final String mode) {
if (isOverlayAction(mode)) {
clearOverlayAction(mode);
} else {
setOverlayAction(mode);
}
}
public void zoom(final Point mapPoint, final int steps) {
final Viewport2D viewport = getViewport();
final BoundingBox boundingBox = getBoundingBox();
final double width = boundingBox.getWidth();
final double height = boundingBox.getHeight();
final double scale = getScale();
long newScale;
if (steps == 0) {
return;
} else if (steps < 0) {
newScale = getZoomInScale(scale, -steps);
} else {
newScale = getZoomOutScale(scale, steps);
}
if (newScale > 0 && Math.abs(newScale - scale) > 0.0001) {
final double unitsPerPixel = viewport.getUnitsPerPixel(newScale);
final int viewWidthPixels = viewport.getViewWidthPixels();
final double newWidth = viewWidthPixels * unitsPerPixel;
final int viewHeightPixels = viewport.getViewHeightPixels();
final double newHeight = viewHeightPixels * unitsPerPixel;
final double x = mapPoint.getX();
final double x1 = boundingBox.getMinX();
final double deltaX = x - x1;
final double percentX = deltaX / width;
final double newDeltaX = newWidth * percentX;
final double newX1 = x - newDeltaX;
final double y = mapPoint.getY();
final double y1 = boundingBox.getMinY();
final double deltaY = y - y1;
final double percentY = deltaY / height;
final double newDeltaY = newHeight * percentY;
final double newY1 = y - newDeltaY;
final GeometryFactory newGeometryFactory = boundingBox.getGeometryFactory();
final BoundingBox newBoundingBox = newGeometryFactory.newBoundingBox(newX1, newY1,
newX1 + newWidth, newY1 + newHeight);
setBoundingBox(newBoundingBox);
}
}
public void zoomIn() {
final double scale = getScale();
final long newScale = getZoomInScale(scale, 1);
setScale(newScale);
}
public void zoomNext() {
setZoomHistoryIndex(this.zoomHistoryIndex + 1);
}
public void zoomOut() {
final double scale = getScale();
final long newScale = getZoomOutScale(scale, 1);
setScale(newScale);
}
public void zoomPrevious() {
setZoomHistoryIndex(this.zoomHistoryIndex - 1);
}
/**
* Zoom to the bounding box with a 5% padding on each side
*
* @param boundingBox
*/
public void zoomToBoundingBox(BoundingBox boundingBox) {
final GeometryFactory geometryFactory = getGeometryFactory();
boundingBox = boundingBox.convert(geometryFactory).expandPercent(0.1);
setBoundingBox(boundingBox);
}
public void zoomToGeometry(final Geometry geometry) {
if (geometry != null) {
final GeometryFactory geometryFactory = getGeometryFactory();
final Geometry convertedGeometry = geometry.convertGeometry(geometryFactory);
final BoundingBox boudingBox = convertedGeometry.getBoundingBox();
zoomToBoundingBox(boudingBox);
}
}
public void zoomToLayer(final Layer layer) {
if (layer != null && layer.isExists() && layer.isVisible()) {
final BoundingBox boundingBox = layer.getBoundingBox(true);
zoomToBoundingBox(boundingBox);
}
}
public void zoomToRecord(final Record record) {
if (record != null) {
final Geometry geometry = record.getGeometry();
zoomToGeometry(geometry);
}
}
public void zoomToSelected() {
zoomToSelected(this.project);
}
public void zoomToSelected(final Layer layer) {
final BoundingBox boundingBox = layer.getSelectedBoundingBox();
if (!boundingBox.isEmpty()) {
zoomToBoundingBox(boundingBox);
}
}
public void zoomToWorld() {
final GeometryFactory geometryFactory = getGeometryFactory();
final CoordinateSystem coordinateSystem = geometryFactory.getCoordinateSystem();
final BoundingBox boundingBox = coordinateSystem.getAreaBoundingBox();
setBoundingBox(boundingBox);
}
}