package com.revolsys.swing.map.layer;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import com.revolsys.collection.map.MapEx;
import com.revolsys.geometry.cs.CoordinateSystem;
import com.revolsys.geometry.cs.GeographicCoordinateSystem;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.util.BoundingBoxUtil;
import com.revolsys.io.BaseCloseable;
import com.revolsys.io.FileUtil;
import com.revolsys.io.file.FileConnectionManager;
import com.revolsys.io.file.FileNameExtensionFilter;
import com.revolsys.io.file.FolderConnectionRegistry;
import com.revolsys.io.file.Paths;
import com.revolsys.logging.Logs;
import com.revolsys.record.io.RecordStoreConnectionManager;
import com.revolsys.record.io.RecordStoreConnectionRegistry;
import com.revolsys.record.io.format.json.Json;
import com.revolsys.spring.resource.FileSystemResource;
import com.revolsys.spring.resource.PathResource;
import com.revolsys.spring.resource.Resource;
import com.revolsys.swing.SwingUtil;
import com.revolsys.swing.component.BasePanel;
import com.revolsys.swing.component.ValueField;
import com.revolsys.swing.layout.GroupLayouts;
import com.revolsys.swing.map.MapPanel;
import com.revolsys.swing.map.ProjectFrame;
import com.revolsys.swing.menu.MenuFactory;
import com.revolsys.util.Exceptions;
import com.revolsys.util.PreferencesUtil;
import com.revolsys.util.Property;
import com.revolsys.util.Strings;
import com.revolsys.util.number.Integers;
import com.revolsys.webservice.WebServiceConnectionRegistry;
public class Project extends LayerGroup {
private static WeakReference<Project> projectReference = new WeakReference<>(null);
static {
final MenuFactory menu = MenuFactory.getMenu(Project.class);
menu.deleteMenuItem("layer", "Delete");
}
public static synchronized void clearProject(final Project project) {
final WeakReference<Project> projectReference = Project.projectReference;
if (Project.projectReference != null) {
final Project currentProject = projectReference.get();
if (currentProject == project) {
Project.projectReference = new WeakReference<>(null);
}
}
}
public static synchronized Project get() {
return Project.projectReference.get();
}
public static synchronized void set(final Project project) {
Project.projectReference = new WeakReference<>(project);
}
private BaseMapLayerGroup baseMapLayers = new BaseMapLayerGroup();
private FolderConnectionRegistry folderConnections = new FolderConnectionRegistry("Project");
private WebServiceConnectionRegistry webServices = new WebServiceConnectionRegistry("Project");
private BoundingBox initialBoundingBox;
private RecordStoreConnectionRegistry recordStores = new RecordStoreConnectionRegistry("Project");
private Resource resource;
private BoundingBox viewBoundingBox = BoundingBox.empty();
private Map<String, BoundingBox> zoomBookmarks = new LinkedHashMap<>();
public Project() {
this("Project");
}
public Project(final String name) {
super(name);
setType("Project");
this.baseMapLayers.setLayerGroup(this);
setGeometryFactory(GeometryFactory.worldMercator());
}
private void addChangedLayers(final LayerGroup group, final List<Layer> layersWithChanges) {
for (final Layer layer : group) {
if (layer instanceof LayerGroup) {
final LayerGroup subGroup = (LayerGroup)layer;
addChangedLayers(subGroup, layersWithChanges);
} else if (layer.isHasChanges()) {
layersWithChanges.add(layer);
}
}
}
public void addZoomBookmark(final String name, final BoundingBox boundingBox) {
if (name != null && boundingBox != null) {
this.zoomBookmarks.put(name, boundingBox);
}
}
@Override
public void delete() {
super.delete();
this.baseMapLayers = null;
this.viewBoundingBox = null;
this.zoomBookmarks = null;
}
public BaseMapLayerGroup getBaseMapLayers() {
return this.baseMapLayers;
}
@Override
public Path getDirectory() {
final Path directory = getProjectDirectory();
if (directory != null) {
final Path layersDirectory = directory.resolve("Layers");
Paths.createDirectories(layersDirectory);
if (Files.isDirectory(layersDirectory)) {
return layersDirectory;
}
}
return null;
}
public FolderConnectionRegistry getFolderConnections() {
return this.folderConnections;
}
@Override
protected Path getGroupSettingsDirectory(final Path directory) {
return directory;
}
public BoundingBox getInitialBoundingBox() {
return this.initialBoundingBox;
}
@Override
@SuppressWarnings("unchecked")
public <V extends Layer> V getLayer(final String name) {
if (name.equals("Base Maps")) {
return (V)this.baseMapLayers;
} else {
return (V)super.getLayer(name);
}
}
@Override
public Project getProject() {
return this;
}
public Path getProjectDirectory() {
if (this.resource == null) {
final Path directory = getSaveAsDirectory();
return directory;
}
if (this.resource instanceof FileSystemResource) {
final FileSystemResource fileResource = (FileSystemResource)this.resource;
final File directory = fileResource.getFile();
if (!directory.exists()) {
directory.mkdirs();
}
if (directory.isDirectory()) {
return directory.toPath();
}
} else if (this.resource instanceof PathResource) {
final PathResource pathResource = (PathResource)this.resource;
final Path directory = pathResource.getPath();
if (!Files.exists(directory)) {
try {
Files.createDirectories(directory);
} catch (final IOException e) {
throw Exceptions.wrap(e);
}
}
if (Files.isDirectory(directory)) {
return directory;
}
}
return null;
}
public RecordStoreConnectionRegistry getRecordStores() {
return this.recordStores;
}
public Path getSaveAsDirectory() {
File directory = null;
final JFileChooser fileChooser = SwingUtil.newFileChooser("Save Project",
"com.revolsys.swing.map.project", "directory");
fileChooser.setFileFilter(new FileNameExtensionFilter("Project", "rgmap"));
fileChooser.setMultiSelectionEnabled(false);
final int returnVal = fileChooser.showSaveDialog(SwingUtil.getActiveWindow());
if (returnVal == JFileChooser.APPROVE_OPTION) {
directory = fileChooser.getSelectedFile();
final String fileExtension = FileUtil.getFileNameExtension(directory);
if (!"rgmap".equals(fileExtension)) {
directory = new File(directory.getParentFile(), directory.getName() + ".rgmap");
}
PreferencesUtil.setUserString("com.revolsys.swing.map.project", "directory",
directory.getParent());
if (directory.exists()) {
FileUtil.deleteDirectory(directory);
}
directory.mkdirs();
this.resource = new FileSystemResource(directory);
return directory.toPath();
} else {
return null;
}
}
public BoundingBox getViewBoundingBox() {
return this.viewBoundingBox;
}
public WebServiceConnectionRegistry getWebServices() {
return this.webServices;
}
public Map<String, BoundingBox> getZoomBookmarks() {
return this.zoomBookmarks;
}
@Override
protected void importProject(final Project importProject) {
final List<Layer> importLayers = importProject.getLayers();
addLayers(importLayers);
final BaseMapLayerGroup importBaseMaps = importProject.getBaseMapLayers();
final BaseMapLayerGroup baseMaps = getBaseMapLayers();
baseMaps.addLayers(importBaseMaps);
}
@Override
protected ValueField newPropertiesTabGeneralPanelSource(final BasePanel parent) {
final ValueField panel = super.newPropertiesTabGeneralPanelSource(parent);
if (this.resource != null) {
SwingUtil.addLabelledReadOnlyTextField(panel, "URL", this.resource.getURL());
GroupLayouts.makeColumns(panel, 2, true);
}
return panel;
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("hasSelectedRecords")) {
final boolean selected = isHasSelectedRecords();
firePropertyChange("hasSelectedRecords", !selected, selected);
}
}
public BaseMapLayerGroup readBaseMapsLayers(final Resource resource) {
final Resource baseMapsResource = resource.newChildResource("Base Maps");
final Resource layerGroupResource = baseMapsResource.newChildResource("rgLayerGroup.rgobject");
if (layerGroupResource.exists()) {
final Resource oldResource = Resource.setBaseResource(baseMapsResource);
try {
final Map<String, Object> properties = Json.toMap(layerGroupResource);
this.baseMapLayers.loadLayers(properties);
boolean hasVisible = false;
if (this.baseMapLayers != null) {
for (final Layer layer : this.baseMapLayers) {
if (hasVisible) {
layer.setVisible(false);
} else {
hasVisible = layer.isVisible();
}
}
}
} finally {
Resource.setBaseResource(oldResource);
}
}
return this.baseMapLayers;
}
protected void readLayers(final Resource resource) {
final Resource layerGroupResource = resource.newChildResource("rgLayerGroup.rgobject");
if (!layerGroupResource.exists()) {
Logs.error(this, "File not found: " + layerGroupResource);
} else {
final Resource oldResource = Resource.setBaseResource(resource);
try {
final Map<String, Object> properties = Json.toMap(layerGroupResource);
loadLayers(properties);
} catch (final Throwable e) {
Logs.error(this, "Unable to read: " + layerGroupResource, e);
} finally {
Resource.setBaseResource(oldResource);
}
}
}
public void readProject(final Object source) {
this.resource = Resource.getResource(source);
if (this.resource.exists()) {
String name;
try (
final BaseCloseable booleanValueCloseable = eventsDisabled()) {
final Resource layersDir = this.resource.newChildResource("Layers");
readProperties(layersDir);
final RecordStoreConnectionRegistry oldRecordStoreConnections = RecordStoreConnectionRegistry
.getForThread();
try {
final Resource recordStoresDirectory = this.resource.newChildResource("Record Stores");
if (!recordStoresDirectory.exists()) {
final Resource dataStoresDirectory = this.resource.newChildResource("Data Stores");
if (dataStoresDirectory.exists()) {
final File file = dataStoresDirectory.getFile();
file.renameTo(new File(file.getParentFile(), "Record Stores"));
}
}
final boolean readOnly = isReadOnly();
final RecordStoreConnectionRegistry recordStores = new RecordStoreConnectionRegistry(
"Project", recordStoresDirectory, readOnly);
setRecordStores(recordStores);
RecordStoreConnectionRegistry.setForThread(recordStores);
final Resource folderConnectionsDirectory = this.resource
.newChildResource("Folder Connections");
this.folderConnections = new FolderConnectionRegistry("Project",
folderConnectionsDirectory, readOnly);
final Resource webServicesDirectory = this.resource.newChildResource("Web Services");
this.webServices = new WebServiceConnectionRegistry("Project", webServicesDirectory,
readOnly);
readLayers(layersDir);
readBaseMapsLayers(this.resource);
} finally {
RecordStoreConnectionRegistry.setForThread(oldRecordStoreConnections);
}
name = getName();
setName(null);
}
setName(name);
}
}
protected void readProperties(final Resource resource) {
final Resource layerGroupResource = resource.newChildResource("rgLayerGroup.rgobject");
if (!layerGroupResource.exists()) {
Logs.error(this, "File not found: " + layerGroupResource);
} else {
final Resource oldResource = Resource.setBaseResource(resource);
try {
final Map<String, Object> properties = Json.toMap(layerGroupResource);
setProperties(properties);
} catch (final Throwable e) {
Logs.error(this, "Unable to read: " + layerGroupResource, e);
} finally {
Resource.setBaseResource(oldResource);
}
}
}
public void removeZoomBookmark(final String name) {
if (name != null) {
this.zoomBookmarks.remove(name);
}
}
public void reset() {
clear();
setName("Project");
this.baseMapLayers.clear();
this.recordStores = new RecordStoreConnectionRegistry("Project");
this.folderConnections = new FolderConnectionRegistry("Project");
this.webServices = new WebServiceConnectionRegistry("Project");
this.initialBoundingBox = null;
this.resource = null;
this.viewBoundingBox = BoundingBox.empty();
this.zoomBookmarks.clear();
firePropertyChange("reset", false, true);
}
public void save() {
}
public boolean saveAllSettings() {
if (isReadOnly()) {
return true;
} else {
final Path directory = getDirectory();
final boolean saveAllSettings = super.saveAllSettings(directory);
if (saveAllSettings) {
final RecordStoreConnectionManager recordStoreConnectionManager = RecordStoreConnectionManager
.get();
final RecordStoreConnectionRegistry recordStoreConnections = recordStoreConnectionManager
.getConnectionRegistry("Project");
recordStoreConnections.saveAs(this.resource, "Record Stores");
final FileConnectionManager fileConnectionManager = FileConnectionManager.get();
final FolderConnectionRegistry folderConnections = fileConnectionManager
.getConnectionRegistry("Project");
folderConnections.saveAs(this.resource, "Folder Connections");
}
return saveAllSettings;
}
}
public Path saveAllSettingsAs() {
final Resource resource = this.resource;
try {
this.resource = null;
final Path projectPath = getSaveAsDirectory();
if (projectPath != null) {
setName(Paths.getBaseName(projectPath));
saveAllSettings();
}
return projectPath;
} finally {
if (this.resource == null) {
this.resource = resource;
}
}
}
public boolean saveChangesWithPrompt() {
return saveChangesWithPrompt(JOptionPane.YES_NO_CANCEL_OPTION);
}
public boolean saveChangesWithPrompt(final int optionType) {
if (isReadOnly()) {
return true;
} else {
final List<Layer> layersWithChanges = new ArrayList<>();
addChangedLayers(this, layersWithChanges);
if (layersWithChanges.isEmpty()) {
return true;
} else {
final MapPanel mapPanel = getMapPanel();
final JLabel message = new JLabel(
"<html><body><p><b>The following layers have un-saved changes.</b></p>"
+ "<p><b>Do you want to save the changes before continuing?</b></p><ul><li>"
+ Strings.toString("</li>\n<li>", layersWithChanges) + "</li></ul></body></html>");
final int option = JOptionPane.showConfirmDialog(mapPanel, message, "Save Changes",
optionType, JOptionPane.WARNING_MESSAGE);
if (option == JOptionPane.CANCEL_OPTION) {
return false;
} else if (option == JOptionPane.NO_OPTION) {
return true;
} else {
for (final Iterator<Layer> iterator = layersWithChanges.iterator(); iterator.hasNext();) {
final Layer layer = iterator.next();
if (layer.saveChanges()) {
iterator.remove();
}
}
if (layersWithChanges.isEmpty()) {
return true;
} else {
final JLabel message2 = new JLabel(
"<html><body><p><b>The following layers could not be saved.</b></p>"
+ "<p><b>Do you want to ignore these changes and continue?</b></p><ul><li>"
+ Strings.toString("</li>\n<li>", layersWithChanges) + "</li></ul></body></html>");
final int option2 = JOptionPane.showConfirmDialog(mapPanel, message2, "Ignore Changes",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (option2 == JOptionPane.CANCEL_OPTION) {
return false;
} else {
return true;
}
}
}
}
}
}
@Override
protected boolean saveSettingsDo(final Path directory) {
boolean saved = true;
FileUtil.deleteDirectory(directory.toFile(), false);
Paths.createDirectories(directory);
saved &= super.saveSettingsDo(directory);
final Path projectPath = getProjectDirectory();
if (projectPath == null) {
return false;
} else {
final Path baseMapsPath = projectPath.resolve("Base Maps");
FileUtil.deleteDirectory(baseMapsPath.toFile(), false);
final LayerGroup baseMapLayers = getBaseMapLayers();
if (baseMapLayers != null) {
saved &= baseMapLayers.saveAllSettings(projectPath);
}
return saved;
}
}
public boolean saveSettingsWithPrompt() {
if (isReadOnly()) {
return true;
} else {
final MapPanel mapPanel = getMapPanel();
final JLabel message = new JLabel(
"<html><body><p><b>Save changes to project?</b></p></body></html>");
final int option = JOptionPane.showConfirmDialog(mapPanel, message, "Save Changes",
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (option == JOptionPane.CANCEL_OPTION) {
return false;
} else if (option == JOptionPane.NO_OPTION) {
return true;
} else {
if (saveAllSettings()) {
return true;
} else {
final JLabel message2 = new JLabel("<html><body><p>Saving project failed.</b></p>"
+ "<p><b>Do you want to ignore any changes and continue?</b></p></body></html>");
final int option2 = JOptionPane.showConfirmDialog(mapPanel, message2, "Ignore Changes",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (option2 == JOptionPane.CANCEL_OPTION) {
return false;
} else {
return true;
}
}
}
}
}
public boolean saveWithPrompt() {
boolean result = saveChangesWithPrompt();
if (result) {
result = saveSettingsWithPrompt();
}
return result;
}
public void setFolderConnections(final FolderConnectionRegistry folderConnections) {
this.folderConnections = folderConnections;
}
@Override
public void setGeometryFactory(final GeometryFactory geometryFactory) {
super.setGeometryFactory(geometryFactory);
}
@Override
public void setProperty(final String name, final Object value) {
if ("srid".equals(name)) {
try {
final Integer srid = Integers.toValid(value);
if (srid != null) {
setGeometryFactory(GeometryFactory.floating3(srid));
}
} catch (final Throwable t) {
}
} else if ("viewBoundingBox".equals(name)) {
if (value != null) {
final BoundingBox viewBoundingBox = BoundingBox.newBoundingBox(value.toString());
if (!BoundingBoxUtil.isEmpty(viewBoundingBox)) {
this.initialBoundingBox = viewBoundingBox;
setViewBoundingBoxAndGeometryFactory(viewBoundingBox);
}
}
} else {
super.setProperty(name, value);
}
}
public void setRecordStores(final RecordStoreConnectionRegistry recordStores) {
this.recordStores = recordStores;
}
public void setSrid(final Number srid) {
if (srid != null) {
final GeometryFactory geometryFactory = GeometryFactory.floating3(srid.intValue());
setGeometryFactory(geometryFactory);
}
}
public void setViewBoundingBox(final BoundingBox viewBoundingBox) {
final BoundingBox oldBoundingBox = this.viewBoundingBox;
final boolean bboxUpdated = setViewBoundingBoxDo(viewBoundingBox);
if (bboxUpdated) {
firePropertyChange("viewBoundingBox", oldBoundingBox, this.viewBoundingBox);
}
}
public void setViewBoundingBoxAndGeometryFactory(final BoundingBox viewBoundingBox) {
if (!Property.isEmpty(viewBoundingBox)) {
final BoundingBox oldBoundingBox = this.viewBoundingBox;
final boolean bboxUpdated = setViewBoundingBoxDo(viewBoundingBox);
final GeometryFactory oldGeometryFactory = getGeometryFactory();
final GeometryFactory geometryFactory = viewBoundingBox.getGeometryFactory();
final boolean geometryFactoryUpdated = setGeometryFactoryDo(geometryFactory);
if (geometryFactoryUpdated) {
fireGeometryFactoryChanged(oldGeometryFactory, geometryFactory);
}
if (bboxUpdated) {
firePropertyChange("viewBoundingBox", oldBoundingBox, this.viewBoundingBox);
}
}
}
protected boolean setViewBoundingBoxDo(BoundingBox viewBoundingBox) {
if (Property.isEmpty(viewBoundingBox)) {
return false;
} else {
// TODO really should be min scale
double minDimension;
if (viewBoundingBox.getCoordinateSystem() instanceof GeographicCoordinateSystem) {
minDimension = 0.000005;
} else {
minDimension = 0.5;
}
final double width = viewBoundingBox.getWidth();
if (width < minDimension) {
viewBoundingBox = viewBoundingBox.expand((minDimension - width) / 2, 0);
}
final double height = viewBoundingBox.getHeight();
if (height < minDimension) {
viewBoundingBox = viewBoundingBox.expand(0, (minDimension - height) / 2);
}
final BoundingBox oldBoundingBox = this.viewBoundingBox;
this.viewBoundingBox = viewBoundingBox;
return !viewBoundingBox.equals(oldBoundingBox);
}
}
public void setWebServices(final WebServiceConnectionRegistry webServices) {
this.webServices = webServices;
}
public void setZoomBookmarks(final Map<String, ?> zoomBookmarks) {
final Map<String, BoundingBox> bookmarks = new LinkedHashMap<>();
if (zoomBookmarks != null) {
for (final Entry<String, ?> entry : zoomBookmarks.entrySet()) {
final String name = entry.getKey();
final Object object = entry.getValue();
if (object != null && name != null) {
try {
BoundingBox boundingBox = null;
if (object instanceof BoundingBox) {
boundingBox = (BoundingBox)object;
} else if (object instanceof Geometry) {
final Geometry geometry = (Geometry)object;
boundingBox = geometry.getBoundingBox();
} else if (object != null) {
final String wkt = object.toString();
boundingBox = BoundingBox.newBoundingBox(wkt);
}
if (boundingBox != null) {
bookmarks.put(name, boundingBox);
}
} catch (final Throwable e) {
Logs.error(this, "Not a valid bounding box " + name + "=" + object, e);
}
}
}
this.zoomBookmarks = bookmarks;
}
}
@Override
public MapEx toMap() {
final MapEx map = super.toMap();
BoundingBox boundingBox = getViewBoundingBox();
if (!BoundingBoxUtil.isEmpty(boundingBox)) {
BoundingBox defaultBoundingBox = null;
final GeometryFactory geometryFactory = getGeometryFactory();
if (geometryFactory != null) {
final CoordinateSystem coordinateSystem = geometryFactory.getCoordinateSystem();
if (coordinateSystem != null) {
defaultBoundingBox = coordinateSystem.getAreaBoundingBox();
}
boundingBox = boundingBox.convert(geometryFactory);
}
addToMap(map, "viewBoundingBox", boundingBox, defaultBoundingBox);
final Map<String, BoundingBox> zoomBookmarks = getZoomBookmarks();
addToMap(map, "zoomBookmarks", zoomBookmarks);
}
final ProjectFrame projectFrame = ProjectFrame.get(this);
if (projectFrame != null) {
final Rectangle frameBounds = projectFrame.getBounds();
if (frameBounds != null) {
map.put("frameBounds",
Arrays.asList(frameBounds.x, frameBounds.y, frameBounds.width, frameBounds.height));
}
}
return map;
}
}