package com.revolsys.swing.map.layer; import java.awt.Window; import java.beans.PropertyChangeEvent; import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileFilter; import com.revolsys.collection.Parent; import com.revolsys.collection.list.Lists; import com.revolsys.collection.map.MapEx; import com.revolsys.datatype.DataTypes; import com.revolsys.elevation.cloud.PointCloudReaderFactory; import com.revolsys.elevation.gridded.GriddedElevationModelReadFactory; import com.revolsys.elevation.tin.TriangulatedIrregularNetworkReadFactory; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.io.FileUtil; import com.revolsys.io.IoFactory; import com.revolsys.io.PathName; import com.revolsys.io.PathUtil; import com.revolsys.io.file.FileNameExtensionFilter; import com.revolsys.io.file.Paths; import com.revolsys.io.map.MapObjectFactory; import com.revolsys.logging.Logs; import com.revolsys.raster.GeoreferencedImageReadFactory; import com.revolsys.record.io.RecordReaderFactory; import com.revolsys.record.io.format.json.Json; import com.revolsys.spring.resource.FileSystemResource; import com.revolsys.spring.resource.Resource; import com.revolsys.swing.Icons; import com.revolsys.swing.SwingUtil; import com.revolsys.swing.map.layer.elevation.gridded.GriddedElevationModelLayer; import com.revolsys.swing.map.layer.elevation.tin.TriangulatedIrregularNetworkLayer; import com.revolsys.swing.map.layer.pointcloud.PointCloudLayer; import com.revolsys.swing.map.layer.raster.GeoreferencedImageLayer; import com.revolsys.swing.map.layer.record.FileRecordLayer; import com.revolsys.swing.map.layer.record.renderer.GeometryStyleRenderer; import com.revolsys.swing.map.layer.record.style.GeometryStyle; import com.revolsys.swing.menu.MenuFactory; import com.revolsys.swing.menu.Menus; import com.revolsys.swing.parallel.Invoke; import com.revolsys.swing.tree.node.file.PathTreeNode; import com.revolsys.swing.tree.node.layer.LayerGroupTreeNode; import com.revolsys.util.OS; import com.revolsys.util.Property; import com.revolsys.util.UrlUtil; public class LayerGroup extends AbstractLayer implements Parent<Layer>, Iterable<Layer> { static { final MenuFactory menu = MenuFactory.getMenu(LayerGroup.class); menu.addGroup(0, "group"); Menus.<LayerGroup> addMenuItem(menu, "group", "Add Group", Icons.getIconWithBadge(PathTreeNode.ICON_FOLDER, "add"), LayerGroup::actionAddLayerGroup, false); Menus.<LayerGroup> addMenuItem(menu, "group", "Open File Layer...", "page_add", LayerGroup::actionOpenFileLayer, false); Menus.<LayerGroup> addMenuItem(menu, "group", "Import Project...", "map:import", LayerGroup::actionImportProject, false); } private static Layer getLayer(LayerGroup group, final String name) { for (final String path : PathUtil.getPathElements(PathUtil.getPath(name))) { final Layer layer = getLayerByName(group, path); if (layer instanceof LayerGroup) { group = (LayerGroup)layer; } else { return null; } } if (group != null) { final String layerName = PathUtil.getName(name); return getLayerByName(group, layerName); } return null; } private static Layer getLayerByName(final LayerGroup group, final String layerName) { for (final Layer layer : group.getLayers()) { if (layer.getName().equals(layerName)) { return layer; } } return null; } public static LayerGroup newLayer(final Map<String, Object> properties) { final LayerGroup layerGroup = new LayerGroup(); layerGroup.loadLayers(properties); return layerGroup; } private boolean singleLayerVisible = false; private boolean deleted = false; private List<Layer> layers = new ArrayList<>(); public LayerGroup() { this(null); } public LayerGroup(final String name) { super("layerGroup"); setVisible(true); setName(name); setRenderer(new LayerGroupRenderer(this)); setInitialized(true); setOpen(true); } private LayerGroup actionAddLayerGroup() { final String name = JOptionPane.showInputDialog(SwingUtil.getActiveWindow(), "Enter the name of the new Layer Group.", "Add Layer Group", JOptionPane.PLAIN_MESSAGE); if (Property.hasValue(name)) { final LayerGroup newGroup = new LayerGroup(name); addLayer(newGroup); return newGroup; } else { return null; } } private void actionImportProject() { final JFileChooser fileChooser = SwingUtil.newFileChooser("Import Project", "com.revolsys.swing.map.project", "directory"); final FileNameExtensionFilter filter = new FileNameExtensionFilter("Project (*.rgmap)", "rgmap"); fileChooser.setAcceptAllFileFilterUsed(true); fileChooser.addChoosableFileFilter(filter); fileChooser.setFileFilter(filter); if (!OS.isMac()) { fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); } final Window window = SwingUtil.getActiveWindow(); final int returnVal = fileChooser.showOpenDialog(window); if (returnVal == JFileChooser.APPROVE_OPTION) { final File projectDirectory = fileChooser.getSelectedFile(); if (projectDirectory != null && projectDirectory.exists()) { final Project importProject = new Project(); importProject.readProject(projectDirectory); importProject(importProject); } } } private void actionOpenFileLayer() { final Window window = SwingUtil.getActiveWindow(); final JFileChooser fileChooser = SwingUtil.newFileChooser(getClass(), "currentDirectory"); fileChooser.setMultiSelectionEnabled(true); final Set<String> allImageExtensions = new TreeSet<>(); final List<FileNameExtensionFilter> imageFileFilters = IoFactory .newFileFilters(allImageExtensions, GeoreferencedImageReadFactory.class); final Set<String> allRecordExtensions = new TreeSet<>(); final List<FileNameExtensionFilter> recordFileFilters = IoFactory .newFileFilters(allRecordExtensions, RecordReaderFactory.class); final Set<String> allElevationModelExtensions = new TreeSet<>(); final List<FileNameExtensionFilter> elevationModelFileFilters = IoFactory .newFileFilters(allElevationModelExtensions, GriddedElevationModelReadFactory.class); final Set<String> allTinExtensions = new TreeSet<>(); final List<FileNameExtensionFilter> tinFileFilters = IoFactory.newFileFilters(allTinExtensions, GriddedElevationModelReadFactory.class); final Set<String> allExtensions = new TreeSet<>(); allExtensions.addAll(allRecordExtensions); allExtensions.addAll(allImageExtensions); allExtensions.addAll(allElevationModelExtensions); allExtensions.addAll(allTinExtensions); final FileNameExtensionFilter allFilter = IoFactory.newFileFilter("All Supported Files", allExtensions); fileChooser.addChoosableFileFilter(allFilter); fileChooser.addChoosableFileFilter( IoFactory.newFileFilter("All Vector/Record Files", allRecordExtensions)); fileChooser .addChoosableFileFilter(IoFactory.newFileFilter("All Image Files", allImageExtensions)); fileChooser.addChoosableFileFilter( IoFactory.newFileFilter("All Gridded Elevation Model Files", allElevationModelExtensions)); fileChooser.addChoosableFileFilter( IoFactory.newFileFilter("All Triangulated Irregular Network Files", allTinExtensions)); for (final List<? extends FileFilter> filters : Arrays.asList(recordFileFilters, imageFileFilters, elevationModelFileFilters, tinFileFilters)) { for (final FileFilter fileFilter : filters) { fileChooser.addChoosableFileFilter(fileFilter); } } fileChooser.setAcceptAllFileFilterUsed(false); fileChooser.setFileFilter(allFilter); final int status = fileChooser.showDialog(window, "Open Files"); if (status == JFileChooser.APPROVE_OPTION) { final Object menuSource = MenuFactory.getMenuSource(); final LayerGroup layerGroup; if (menuSource instanceof LayerGroupTreeNode) { final LayerGroupTreeNode node = (LayerGroupTreeNode)menuSource; layerGroup = node.getGroup(); } else if (menuSource instanceof LayerGroup) { layerGroup = (LayerGroup)menuSource; } else { layerGroup = Project.get(); } for (final File file : fileChooser.getSelectedFiles()) { Invoke.background("Open file: " + FileUtil.getCanonicalPath(file), () -> layerGroup.openFile(file)); } } SwingUtil.saveFileChooserDirectory(getClass(), "currentDirectory", fileChooser); } protected <V extends Layer> void addDescendants(final List<V> layers, final Class<V> layerClass) { addLayers(layers, layerClass); for (final LayerGroup layerGroup : getLayerGroups()) { layerGroup.addDescendants(layers, layerClass); } } public void addLayer(int index, final Layer layer) { synchronized (this.layers) { if (layer != null && !this.layers.contains(layer)) { final String name = layer.getName(); String newName = name; int i = 1; while (hasLayerWithSameName(layer, newName)) { newName = name + i; i++; } layer.setName(newName); if (index < this.layers.size()) { this.layers.add(index, layer); } else { index = this.layers.size(); this.layers.add(layer); } layer.setLayerGroup(this); initialize(layer); fireIndexedPropertyChange("layers", index, null, layer); } } } public boolean addLayer(final Layer layer) { synchronized (this.layers) { if (layer == null || this.layers.contains(layer)) { return false; } else { final int index = this.layers.size(); addLayer(index, layer); return true; } } } public LayerGroup addLayerGroup(final int index, final String name) { synchronized (this.layers) { final Layer layer = getLayer(name); if (layer == null) { final LayerGroup group = new LayerGroup(name); addLayer(index, group); return group; } if (layer instanceof LayerGroup) { return (LayerGroup)layer; } else { throw new IllegalArgumentException("Layer exists with name " + name); } } } public LayerGroup addLayerGroup(final String name) { if (Property.hasValue(name)) { synchronized (this.layers) { final Layer layer = getLayer(name); if (layer == null) { final LayerGroup group = new LayerGroup(name); addLayer(group); return group; } if (layer instanceof LayerGroup) { return (LayerGroup)layer; } else { throw new IllegalArgumentException("Layer exists with name " + name); } } } else { return this; } } public void addLayers(final Iterable<Layer> layers) { if (layers != null) { for (final Layer layer : layers) { addLayer(layer); } } } @SuppressWarnings("unchecked") protected <V extends Layer> void addLayers(final List<V> layers, final Class<V> layerClass) { for (final Layer layer : this.layers) { if (layerClass.isAssignableFrom(layer.getClass())) { layers.add((V)layer); } } } public void addPath(final List<Layer> path) { final LayerGroup layerGroup = getLayerGroup(); if (layerGroup != null) { layerGroup.addPath(path); } path.add(this); } protected <V extends Layer> void addVisibleDescendants(final List<V> layers, final Class<V> layerClass, final double scale) { if (isVisible(scale)) { addVisibleLayers(layers, layerClass, scale); for (final LayerGroup layerGroup : getLayerGroups()) { layerGroup.addVisibleDescendants(layers, layerClass, scale); } } } @SuppressWarnings("unchecked") protected <V extends Layer> void addVisibleLayers(final List<V> layers, final Class<V> layerClass, final double scale) { for (final Layer layer : this.layers) { if (layer.isVisible(scale)) { if (layerClass.isAssignableFrom(layer.getClass())) { layers.add((V)layer); } } } } public void clear() { for (final Layer layer : new ArrayList<>(this.layers)) { layer.delete(); } this.layers = new ArrayList<>(); } public boolean containsLayer(final Layer layer) { return this.layers.contains(layer); } @Override public void delete() { this.deleted = true; synchronized (this.layers) { for (final Iterator<Layer> iterator = this.layers.iterator(); iterator.hasNext();) { final Layer layer = iterator.next(); iterator.remove(); try { layer.delete(); } catch (final Throwable e) { } layer.setLayerGroup(null); Property.removeListener(layer, this); } super.delete(); this.layers.clear(); } } @Override public BoundingBox getBoundingBox() { final GeometryFactory geometryFactory = getGeometryFactory(); BoundingBox boudingBox = geometryFactory.newBoundingBoxEmpty(); for (final Layer layer : this) { final BoundingBox layerBoundingBox = layer.getBoundingBox(); if (!layerBoundingBox.isEmpty()) { boudingBox = boudingBox.expandToInclude(layerBoundingBox); } } return boudingBox; } @Override public BoundingBox getBoundingBox(final boolean visibleLayersOnly) { final GeometryFactory geometryFactory = getGeometryFactory(); BoundingBox boudingBox = geometryFactory.newBoundingBoxEmpty(); if (isExists() && (!visibleLayersOnly || isVisible())) { for (final Layer layer : this) { if (layer.isExists() && (!visibleLayersOnly || layer.isVisible())) { final BoundingBox layerBoundingBox = layer.getBoundingBox(visibleLayersOnly); if (!layerBoundingBox.isEmpty()) { boudingBox = boudingBox.expandToInclude(layerBoundingBox); } } } } return boudingBox; } @Override public List<Layer> getChildren() { return getLayers(); } public <V extends Layer> List<V> getDescenants(final Class<V> layerClass) { final List<V> layers = new ArrayList<>(); addDescendants(layers, layerClass); return layers; } @Override public java.nio.file.Path getDirectory() { final LayerGroup layerGroup = getLayerGroup(); if (layerGroup != null) { final java.nio.file.Path parentDirectory = layerGroup.getDirectory(); if (parentDirectory != null) { final java.nio.file.Path layerDirectory = getGroupSettingsDirectory(parentDirectory); return layerDirectory; } } return null; } @Override public GeometryFactory getGeometryFactory() { final LayerGroup layerGroup = getLayerGroup(); final GeometryFactory geometryFactory = super.getGeometryFactory(); if (geometryFactory == null && layerGroup != null) { return layerGroup.getGeometryFactory(); } else { return geometryFactory; } } protected java.nio.file.Path getGroupSettingsDirectory(final java.nio.file.Path directory) { final String name = getName(); final String groupDirectoryName = FileUtil.getSafeFileName(name); final java.nio.file.Path groupDirectory = Paths.getDirectoryPath(directory, groupDirectoryName); return groupDirectory; } @Override public long getId() { // TODO Auto-generated method stub return 0; } @SuppressWarnings("unchecked") public <V extends Layer> V getLayer(final int i) { if (i < this.layers.size()) { return (V)this.layers.get(i); } else { return null; } } public <V extends Layer> V getLayer(final PathName name) { return getLayer(name.getPath()); } @SuppressWarnings("unchecked") public <V extends Layer> V getLayer(final String name) { return (V)getLayer(this, name); } public Layer getLayerByPath(final List<String> path) { if (path.isEmpty()) { return this; } else { final Layer layer = getLayer(path.get(0)); if (path.size() == 1) { return layer; } else { if (layer instanceof LayerGroup) { final LayerGroup layerGroup = (LayerGroup)layer; return layerGroup.getLayerByPath(path.subList(1, path.size())); } return null; } } } public Layer getLayerByPath(final String layerPath) { final List<String> path = Lists.split(layerPath.replaceAll("^\\s*/+\\s*", ""), "(\\s*/+\\s*)+"); return getLayerByPath(path); } public int getLayerCount() { return this.layers.size(); } public List<LayerGroup> getLayerGroups() { final List<LayerGroup> layerGroups = new ArrayList<>(); for (final Layer layer : this.layers) { if (layer instanceof LayerGroup) { final LayerGroup layerGroup = (LayerGroup)layer; layerGroups.add(layerGroup); } } return layerGroups; } public List<Layer> getLayers() { return new ArrayList<>(this.layers); } public <V extends Layer> List<V> getLayers(final Class<V> layerClass) { final List<V> layers = new ArrayList<>(); addLayers(layers, layerClass); return layers; } public <V extends Layer> List<V> getLayers(final List<PathName> names) { final List<V> layers = new ArrayList<>(); for (final PathName name : names) { final V layer = getLayer(name); if (layer != null) { layers.add(layer); } } return layers; } public <V extends Layer> List<V> getLayers(final PathName... names) { return getLayers(Arrays.asList(names)); } @Override public BoundingBox getSelectedBoundingBox() { BoundingBox boundingBox = super.getSelectedBoundingBox(); if (isExists() && isVisible()) { for (final Layer layer : this) { final BoundingBox layerBoundingBox = layer.getSelectedBoundingBox(); boundingBox = boundingBox.expandToInclude(layerBoundingBox); } } return boundingBox; } @Override protected String getSettingsFileName() { return "rgLayerGroup.rgobject"; } public <V extends Layer> List<V> getVisibleDescendants(final Class<V> layerClass, final double scale) { final List<V> layers = new ArrayList<>(); addVisibleDescendants(layers, layerClass, scale); return layers; } public boolean hasLayerWithSameName(final Layer layer, final String name) { for (final Layer otherLayer : this.layers) { if (layer != otherLayer) { final String layerName = otherLayer.getName(); if (DataTypes.STRING.equals(name, layerName)) { return true; } } } return false; } protected void importProject(final Project importProject) { final List<Layer> importLayers = importProject.getLayers(); addLayers(importLayers); } public int indexOf(final Layer layer) { return this.layers.indexOf(layer); } public void initialize(final Layer layer) { if (getProject() != null) { LayerInitializer.initialize(layer); if (layer instanceof LayerGroup) { final LayerGroup layerGroup = (LayerGroup)layer; for (final Layer child : layerGroup) { initialize(child); } } } } @Override public boolean isDeleted() { return this.deleted; } public boolean isEmpty() { return this.layers.isEmpty(); } @Override public boolean isHasSelectedRecords() { for (final Layer layer : getLayers()) { if (layer.isHasSelectedRecords()) { return true; } } return false; } public boolean isHasVisibleLayer() { for (final Layer layer : getLayers()) { if (layer.isVisible()) { return true; } } return false; } @Override public boolean isQueryable() { return false; } @Override public boolean isQuerySupported() { return false; } public boolean isSingleLayerVisible() { return this.singleLayerVisible; } @Override public boolean isZoomToLayerEnabled() { if (!getBoundingBox().isEmpty()) { return true; } return false; } @Override public Iterator<Layer> iterator() { return getLayers().iterator(); } protected Layer loadLayer(final File file) { final Resource oldResource = Resource .setBaseResource(new FileSystemResource(file.getParentFile())); try { final Map<String, Object> properties = Json.toMap(file); final Layer layer = MapObjectFactory.toObject(properties); if (layer != null) { addLayer(layer); } return layer; } catch (final Throwable t) { Logs.error(this, "Cannot load layer from " + file, t); return null; } finally { Resource.setBaseResource(oldResource); } } @SuppressWarnings("unchecked") protected void loadLayers(final Map<String, Object> properties) { final List<String> layerFiles = (List<String>)properties.remove("layers"); setProperties(properties); if (layerFiles != null) { for (String fileName : layerFiles) { if (!fileName.endsWith("rgobject")) { fileName += "/rgLayerGroup.rgobject"; } final Resource childResource = Resource.getBaseResource(fileName); if (childResource.exists()) { final Object object = MapObjectFactory.toObject(childResource); if (object instanceof Layer) { final Layer layer = (Layer)object; addLayer(layer); } else if (object != null) { Logs.error(this, "Unexpected object type " + object.getClass() + " in " + childResource); } } else { Logs.error(LayerGroup.class, "Cannot find " + childResource); } } } } public void openFile(final File file) { final String extension = FileUtil.getFileNameExtension(file); if ("rgobject".equals(extension)) { loadLayer(file); } else { final URL url = FileUtil.toUrl(file); openFile(url); } } public int openFile(final int index, final File file) { final String extension = FileUtil.getFileNameExtension(file); if ("rgobject".equals(extension)) { loadLayer(file); // TODO index return index; } else { final URL url = FileUtil.toUrl(file); return openFile(index, url); } } public int openFile(int index, final URL url) { final String urlString = url.toString(); final Map<String, Object> properties = new HashMap<>(); properties.put("url", urlString); String name = UrlUtil.getFileBaseName(url); name = FileUtil.fromSafeName(name); properties.put("name", name); Layer layer; if (IoFactory.hasFactory(TriangulatedIrregularNetworkReadFactory.class, url)) { layer = new TriangulatedIrregularNetworkLayer(properties); } else if (IoFactory.hasFactory(GriddedElevationModelReadFactory.class, url)) { layer = new GriddedElevationModelLayer(properties); } else if (IoFactory.hasFactory(GeoreferencedImageReadFactory.class, url)) { layer = new GeoreferencedImageLayer(properties); } else if (IoFactory.hasFactory(PointCloudReaderFactory.class, url)) { layer = new PointCloudLayer(properties); } else if (IoFactory.hasFactory(RecordReaderFactory.class, url)) { final FileRecordLayer recordLayer = new FileRecordLayer(properties); final GeometryStyleRenderer renderer = recordLayer.getRenderer(); renderer.setStyle(GeometryStyle.newStyle()); layer = recordLayer; } else { layer = null; } if (layer != null) { layer.setProperty("showTableView", OS.getPreferenceBoolean("com.revolsys.gis", PREFERENCE_PATH, PREFERENCE_NEW_LAYERS_SHOW_TABLE_VIEW, false)); if (index == -1) { addLayer(layer); } else { addLayer(index++, layer); } } return index; } public void openFile(final URL url) { openFile(-1, url); } public void openFiles(int index, final List<File> files) { for (final File file : files) { index = openFile(index, file); } } public void openFiles(final List<File> files) { openFiles(-1, files); } @Override public void propertyChange(final PropertyChangeEvent event) { super.propertyChange(event); final Object source = event.getSource(); final String propertyName = event.getPropertyName(); if ("visible".equals(propertyName)) { if (isSingleLayerVisible()) { final boolean visible = (Boolean)event.getNewValue(); if (visible) { for (final Layer layer : getLayers()) { if (layer != source) { layer.setVisible(false); } } } } } } @Override protected void refreshAllDo() { for (final Layer layer : this.layers) { layer.refreshAll(); } } @Override protected void refreshDo() { } public Layer removeLayer(final int index) { synchronized (this.layers) { final Layer layer = this.layers.remove(index); fireIndexedPropertyChange("layers", index, layer, null); Property.removeListener(layer, this); if (layer.getLayerGroup() == this) { layer.setLayerGroup(null); } return layer; } } public boolean removeLayer(final Object o) { synchronized (this.layers) { final int index = this.layers.indexOf(o); if (index < 0) { return false; } else { removeLayer(index); return true; } } } public boolean saveAllSettings(final java.nio.file.Path directory) { if (directory == null) { return false; } else { boolean saved = true; final java.nio.file.Path groupDirectory = getGroupSettingsDirectory(directory); if (canSaveSettings(directory)) { saved &= saveSettings(directory); for (final Layer layer : this) { if (layer instanceof LayerGroup) { final LayerGroup layerGroup = (LayerGroup)layer; saved &= layerGroup.saveAllSettings(groupDirectory); } else { saved &= layer.saveSettings(groupDirectory); } } } else { saved = false; } return saved; } } @Override public boolean saveChanges() { boolean saved = super.saveChanges(); for (final Layer layer : this) { saved &= layer.saveChanges(); } return saved; } @Override protected boolean saveSettingsDo(final java.nio.file.Path directory) { final java.nio.file.Path groupDirectory = getGroupSettingsDirectory(directory); return super.saveSettingsDo(groupDirectory); } public void setSingleLayerVisible(final boolean singleLayerVisible) { this.singleLayerVisible = singleLayerVisible; } public void sort() { synchronized (this.layers) { Collections.sort(this.layers); } } @Override public MapEx toMap() { final MapEx map = super.toMap(); map.remove("querySupported"); map.remove("queryable"); map.remove("editable"); map.remove("selectable"); map.remove("selectSupported"); map.put("singleLayerVisible", isSingleLayerVisible()); final List<String> layerFiles = new ArrayList<>(); final List<Layer> layers = getLayers(); for (final Layer layer : layers) { final String layerName = layer.getName(); String layerFileName = FileUtil.getSafeFileName(layerName); if (layer instanceof LayerGroup) { } else { layerFileName += ".rgobject"; } layerFiles.add(layerFileName); } addToMap(map, "layers", layerFiles); return map; } }