/** * NodeTypeStatistics.java * * --Copyright notice-- * * Copyright (c) School of Geography, University of Leeds. * http://www.geog.leeds.ac.uk/ * This software is licensed under 'The Artistic License' which can be found at * the Open Source Initiative website at... * http://www.opensource.org/licenses/artistic-license.php * Please note that the optional Clause 8 does not apply to this code. * * The Standard Version source code, and associated documentation can be found at... * [online] http://www.ccg.leeds.ac.uk/software/ * * This code contains Progress Bar code by Marc Mettes: * http://inversionconsulting.blogspot.com/2008/03/java-jdialog-and-jprogressbar-example.html * licenced under the Creative Commons Attribution License 3.0: * http://creativecommons.org/licenses/by/3.0/us/ * * --End of Copyright notice-- * **/ package uk.ac.leeds.ccg.boundarytypeanalyst; import java.awt.BorderLayout; import java.awt.Dimension; import java.io.*; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import com.esri.arcgis.addins.desktop.Button; import com.esri.arcgis.arcmapui.IMxDocument; import com.esri.arcgis.carto.*; import com.esri.arcgis.framework.IApplication; import com.esri.arcgis.framework.IDocument; import com.esri.arcgis.geodatabase.*; import com.esri.arcgis.geometry.Point; import com.esri.arcgis.interop.AutomationException; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; /** * ArcGIS button addin to calculate node degree and type of links. * * @author Andrew Evans * @version 0.1 */ public class NodeTypeStatistics extends Button { private IApplication app; private IMxDocument mxDoc; private IMap map; private IGeoFeatureLayer[] iGeoFeaturelayers; private TopologyLayer topologyLayer; private IFeatureDatasetProxy ifeatureDatasetProxyGot; private IWorkspaceProxy iWorkspaceProxy; private IWorkspace iWorkspace; private IFeatureWorkspace ifeatureWorkspace; private IFeatureDatasetProxy ifeatureDatasetProxyOpened; private IFeatureClassProxy[] iFeatureClassProxys; private ITopology it; private ITopologyGraph iTopologyGraph; private boolean debug = false; // Change in BoundaryTypeAnalysis. private boolean select = false; // Change in BoundaryTypeAnalysis. private BoundaryTypeAnalyst extension; private JProgressBar pb = new JProgressBar(0,100); private javax.swing.JDialog dialog; // Initialisation methods ------------------------------------------------------- /** * Called when the button is clicked. * * @exception java.io.IOException if there are interop problems. * @exception com.esri.arcgis.interop.AutomationException if the component throws an ArcObjects exception. */ @Override public void onClick() throws IOException, AutomationException { extension = BoundaryTypeAnalyst.getInstance(); initialiseMapVariables(); initProgressBar(); listNodeStats(); } /** * Sets up map objects, feature class and layer arrays. */ private void initialiseMapVariables(){ Object[] variables = extension.getVariables(); app = (IApplication) variables[0]; mxDoc = (IMxDocument) variables[1]; map = (IMap) variables[2]; iGeoFeaturelayers = (IGeoFeatureLayer[]) variables[3]; topologyLayer = (TopologyLayer) variables[4]; ifeatureDatasetProxyGot = (IFeatureDatasetProxy) variables[5]; iWorkspaceProxy = (IWorkspaceProxy) variables[6]; iWorkspace = (IWorkspace) variables[7]; ifeatureWorkspace = (IFeatureWorkspace) variables[8]; ifeatureDatasetProxyOpened = (IFeatureDatasetProxy) variables[9]; iFeatureClassProxys = (IFeatureClassProxy[]) variables[10]; it = (ITopology) variables[11]; iTopologyGraph = (ITopologyGraph) variables[12]; debug = ((Boolean) variables[13]).booleanValue(); select = ((Boolean) variables[14]).booleanValue(); } /** * Sets up progress bar. */ public void initProgressBar () { pb.setPreferredSize(new Dimension(175,20)); pb.setString("Working"); pb.setStringPainted(true); pb.setValue(0); JLabel label = new JLabel("Progress: "); JPanel center_panel = new JPanel(); center_panel.add(label); center_panel.add(pb); dialog = new javax.swing.JDialog((java.awt.Frame)null, "Node Type Statistics"); dialog.getContentPane().add(center_panel, BorderLayout.CENTER); dialog.pack(); dialog.setVisible(true); dialog.setLocationRelativeTo(null); // centre on screen dialog.toFront(); // raise above other java windows } // Analysis methods ------------------------------------------------------- /** * Method for listing statistics associated with nodes in a topology. * Lists nodes and the layers they link. For example, if two layers * have a node at the same point, the node will list one example of these two * layers being combined at a node. */ private void listNodeStats() { try { pb.setValue(0); if (debug) System.out.println("Starting listNodeStats analysis"); // Get the topology graph and nodes. IEnumTopologyNode iEnumTopologyNodes = iTopologyGraph.getNodes(); iEnumTopologyNodes.reset(); // Loop through nodes and gather data. // We're going to loop through the nodes, and use them to select // the edges in the original layers. Note that attempts to // utilise the parent edges from topology edges failed. See: // http://forums.arcgis.com/threads/58588-Topology-parents-java.lang.NoSuchFieldError-m_pFC-issue ITopologyNode iTopologyNode = iEnumTopologyNodes.next(); FeatureCursor featureCursor = null; IFeature feature = null; SpatialFilter spatialFilter = new SpatialFilter(); int countNodes = 0; // Couple of arrays to hold links and associated layer class names. int[][] connections = new int[iTopologyGraph.getNodes().getCount()][iGeoFeaturelayers.length]; String[][] names = new String[iTopologyGraph.getNodes().getCount()][]; if (debug) System.out.println("Number of nodes = " + iTopologyGraph.getNodes().getCount()); if (debug) System.out.println("Names = " + names.length); // Run through the nodes in the topology. while (iTopologyNode != null) { pb.setValue((int)((50.0 / iTopologyGraph.getNodes().getCount()) * countNodes)); if (debug) System.out.println("Node " + countNodes + " X = " + ((Point) iTopologyNode.getGeometry()).getX() + " Y = " + ((Point) iTopologyNode.getGeometry()).getY()); // Run through each layer, and query it for all edges that intersect with the node. for (int i = 0; i < iGeoFeaturelayers.length; i++) { spatialFilter.setGeometryByRef((Point) iTopologyNode.getGeometry()); spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects); spatialFilter.setGeometryField(iFeatureClassProxys[i].getShapeFieldName()); featureCursor = new FeatureCursor(iGeoFeaturelayers[i].search(spatialFilter, false)); // Run through the edges and record the number of times each edge type occurs. feature = featureCursor.nextFeature(); while (feature != null) { if (debug) System.out.println("Edge OID = " + feature.getOID() + " Type = " + iGeoFeaturelayers[i].getFeatureClass().getAliasName()); if (select) map.selectFeature(iGeoFeaturelayers[0], feature); connections[countNodes][i]++; names[countNodes] = addName(names[countNodes], iGeoFeaturelayers[i].getFeatureClass().getAliasName()); if (debug) System.out.println("Names[countNodes] = " + names[countNodes].length); feature = featureCursor.nextFeature(); } } if (names[countNodes] == null) { names[countNodes] = new String[1]; names[countNodes][0] = "No edge"; } if (select) mxDoc.getActiveView().refresh(); iTopologyNode = iEnumTopologyNodes.next(); countNodes++; } // End of looping through nodes. pb.setValue(50); // Most of the rest of the code is concerned with finding the number of times a // combination of edge types comes up. The edges should be ordered the same within // the names array, as the layers are processed one at a time (e.g. LayerA, then LayerB, etc.) // however, nodes may connect different sets of layers. We'll work out the number of // times each set appears by first ordering the sets of connection numbers associated with the nodes, // then running through it looking for changes. e.g. // 21 LayerA, LayerA, Layer B // 11 LayerA, LayerB // 21 LayerA, LayerA, LayerB // 10 LayerA // Might order as: // 10 LayerA // 11 LayerA, LayerB // 21 LayerA, LayerA, Layer B // 21 LayerA, LayerA, Layer B // Which we can summarise as: // 1 x LayerA // 1 x LayerA, LayerB // 2 x LayerA, LayerA, Layer B // Note that the important information in the above is the connection numbers, not the names, // which are programmatically inert. The connection numbers represent the names, with, in this case, // the first number being the times LayerA appears as a connection, and the second column being the // number of times LayerB appears as a connection for that node. // First we convert the arrays of connection numbers into Strings, one per node. int numberOfNodes = iTopologyGraph.getNodes().getCount(); String[] connectionStrings = new String[numberOfNodes]; for (int i = 0; i < numberOfNodes; i++) { connectionStrings[i] = ""; for (int j = 0; j < connections[i].length; j++) { connectionStrings[i] = connectionStrings[i] + connections[i][j]; } } pb.setValue(60); // There shouldn't be any null names now, this is legacy code, but // just incase... for (int i = 0; i < numberOfNodes; i++) { System.out.println("i =" + i); for (int j = 0; j < names[i].length; j++) { if (names[i][j] == null) names[i][j] = ""; if (debug) System.out.print(names[i][j]); } if (debug) System.out.println(); } pb.setValue(70); // Next we order the connection numbers (in their String form), dragging the names into // order with them. Uses a simple bubble-sort. boolean swapped = true; int j = 0; String tempConnectionString = ""; String[] tempNameString = null; while (swapped) { swapped = false; j++; for (int i = 0; i < connectionStrings.length - j; i++) { if (connectionStrings[i].compareTo(connectionStrings[i + 1]) > 0) { tempConnectionString = connectionStrings[i]; tempNameString = names[i]; connectionStrings[i] = connectionStrings[i + 1]; names[i] = names[i + 1]; connectionStrings[i + 1] = tempConnectionString; names[i + 1] = tempNameString; swapped = true; } } } if (debug) { for (int i = 0; i < numberOfNodes; i++) { for (int k = 0; k < names[i].length; k++) { System.out.print(names[i][k]); } System.out.println(); } } // Next we run through the sorted list of connection numbers, and // find out the number of times combinations arise. We record these // hits. Note we store these in arrays of size "numberOfNodes" as that's // the maximum, but these will only likely be filled in the first few // spaces - could do with something more efficient here. int hitCounter = 0; int[] countHits = new int[numberOfNodes]; String[] hitNames = new String[numberOfNodes]; for (int i = 0; i < numberOfNodes; i++) { hitNames[i] = ""; } for (int k = 0; k < names[0].length; k++) { hitNames[0] = hitNames[0] + " " + names[0][k]; } String currentString = connectionStrings[0]; pb.setValue(80); // Count uniques and store associated layer names for (int i = 0; i < numberOfNodes; i++) { if (debug) System.out.println(connectionStrings[i]); if (currentString.equals(connectionStrings[i])) { countHits[hitCounter]++; } else { currentString = connectionStrings[i]; hitCounter++; for (int k = 0; k < names[i].length; k++) { hitNames[hitCounter] = hitNames[hitCounter] + " " + names[i][k]; } countHits[hitCounter]++; } } pb.setValue(90); // Print results if (debug) { for (int i = 0; i < numberOfNodes; i++) { if (countHits[i] > 0) { System.out.println(countHits[i] + " : " + hitNames[i]); } } } // Write results File directory = ChangeOutputDirectory.getInstance().getDirectory(); Date now = new Date(System.currentTimeMillis()); String dateString = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(now); String title = ((IDocument)mxDoc).getTitle(); dateString = dateString.replaceAll("[ :]","-"); // Remove some of the date format not allowed in filenames. title = title.replaceAll("[\\/:*?<>|]", ""); // Remove any illegal filename characters from map name. String filepath = directory.getAbsolutePath() + File.separator + title.substring(0,title.lastIndexOf(".")) + "-NodeTypeStatistics-" + dateString + ".csv"; if (debug) System.out.println("Directory = " + filepath); FileWriter fw = new FileWriter(new File (filepath)); BufferedWriter bw = new BufferedWriter(fw); for (int i = 0; i < numberOfNodes; i++) { if (countHits[i] > 0) { bw.write(countHits[i] + "," + hitNames[i]); bw.newLine(); } } bw.flush(); bw.close(); dialog.dispose(); } catch (AutomationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // Utility methods ------------------------------------------------------- /** * Method for adding a String to an array of Strings. * * @param array * @param item * @return */ private String[] addName(String[] array, String item) { if (array == null) { array = new String[1]; array[0] = item; return array; } else { String[] tempArray = new String[array.length]; for (int i = 0; i < array.length; i++) { tempArray[i] = array[i]; } array = new String[tempArray.length + 1]; for (int i = 0; i < tempArray.length; i++) { array[i] = tempArray[i]; } array[array.length - 1] = item; return array; } } }