/** * AllOverlaps.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.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.util.Date; import com.esri.arcgis.addins.desktop.Button; import com.esri.arcgis.arcmapui.IMxDocument; import com.esri.arcgis.carto.IActiveView; import com.esri.arcgis.carto.IGeoFeatureLayer; import com.esri.arcgis.carto.IMap; import com.esri.arcgis.carto.TopologyLayer; import com.esri.arcgis.framework.IApplication; import com.esri.arcgis.framework.IDocument; import com.esri.arcgis.geodatabase.FeatureCursor; import com.esri.arcgis.geodatabase.IEnumTopologyNode; import com.esri.arcgis.geodatabase.IFeature; import com.esri.arcgis.geodatabase.IFeatureClassContainer; import com.esri.arcgis.geodatabase.IFeatureClassProxy; import com.esri.arcgis.geodatabase.IFeatureDatasetProxy; import com.esri.arcgis.geodatabase.IFeatureWorkspace; import com.esri.arcgis.geodatabase.ITopology; import com.esri.arcgis.geodatabase.ITopologyGraph; import com.esri.arcgis.geodatabase.ITopologyNode; import com.esri.arcgis.geodatabase.IWorkspace; import com.esri.arcgis.geodatabase.IWorkspaceProxy; import com.esri.arcgis.geodatabase.SpatialFilter; import com.esri.arcgis.geodatabase.esriSpatialRelEnum; import com.esri.arcgis.geometry.Point; import com.esri.arcgis.geometry.Polyline; import com.esri.arcgis.interop.AutomationException; import com.esri.arcgis.interop.Cleaner; import com.esri.arcgis.geodatabase.*; import com.esri.arcgis.geometry.*; import com.esri.arcgis.carto.*; 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 overlap statistics for each boundary type against each other. * Reports combinations of overlaps, and the names of the overlapping layer types, length of overlaps, and bounding rectangles for these line combinations. * * @author Andrew Evans * @version 0.1 */ public class AllOverlaps 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 = true; // 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; private IActiveView docActiveView = null; private int X_MIN = 0; // For bounding rectangles. private int X_MAX = 1; private int Y_MIN = 2; private int Y_MAX = 3; /** * 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(); allOverlaps(); } /** * 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(); try { docActiveView = (IActiveView) mxDoc.getFocusMap(); } catch (AutomationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 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, "All Line Type Distances"); 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 } /** * Calculates overlaps and prints a results file. */ private void allOverlaps() { double totalXMin = -1; double totalXMax = -1; double totalYMin = -1; double totalYMax = -1; try { int edgeCount = 0; if (debug) System.out.println("starting overlaps"); edgeCount = iTopologyGraph.getEdges().getCount(); if (debug) System.out.println("edgeCount = " + edgeCount); int[][] overlaps = new int[edgeCount][iGeoFeaturelayers.length]; double[] lengths = new double[edgeCount]; double[][] dimensions = new double[edgeCount][4]; // For bounding rectangles. String[][] names = new String[edgeCount][]; SpatialFilter spatialFilter = new SpatialFilter(); IEnumTopologyEdge iEnumTopologyEdges = iTopologyGraph.getEdges(); iEnumTopologyEdges.reset(); ITopologyEdge iTopologyEdge = iEnumTopologyEdges.next(); int edgesProcessed = 0; if (debug) System.out.println("starting processing"); // Loop through edges in topology. String[] shapeFieldNames = new String[iGeoFeaturelayers.length]; for (int i = 0; i < shapeFieldNames.length; i++) shapeFieldNames[i] = iGeoFeaturelayers[i].getFeatureClass().getShapeFieldName(); FeatureCursor cursor = null; IFeature feature = null; if (iTopologyEdge != null) { totalXMin = iTopologyEdge.getGeometry().getEnvelope().getXMin(); totalXMax = iTopologyEdge.getGeometry().getEnvelope().getXMax(); totalYMin = iTopologyEdge.getGeometry().getEnvelope().getYMin(); totalYMax = iTopologyEdge.getGeometry().getEnvelope().getYMax(); } while (iTopologyEdge != null) { if (debug) System.out.println("Processing " + edgesProcessed + " of " + edgeCount); pb.setValue((int)(50.0 / edgeCount) * edgesProcessed); lengths[edgesProcessed] = ((Polyline)iTopologyEdge.getGeometry()).getLength(); spatialFilter.setGeometryByRef((Polyline) iTopologyEdge.getGeometry()); spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelWithin ); if (debug) System.out.println("----new topology feature ----" + iTopologyEdge.getGeometry().getEnvelope().getXMax() + " " + iTopologyEdge.getGeometry().getEnvelope().getYMax() + " " + iTopologyEdge.getGeometry().getEnvelope().getXMin() + " " + iTopologyEdge.getGeometry().getEnvelope().getYMin() + " " + ((Polyline) iTopologyEdge.getGeometry()).getLength()); dimensions[edgesProcessed][X_MIN] = iTopologyEdge.getGeometry().getEnvelope().getXMin(); dimensions[edgesProcessed][X_MAX] = iTopologyEdge.getGeometry().getEnvelope().getXMax(); dimensions[edgesProcessed][Y_MIN] = iTopologyEdge.getGeometry().getEnvelope().getYMin(); dimensions[edgesProcessed][Y_MAX] = iTopologyEdge.getGeometry().getEnvelope().getYMax(); if (dimensions[edgesProcessed][X_MIN] < totalXMin) totalXMin = dimensions[edgesProcessed][X_MIN]; if (dimensions[edgesProcessed][X_MAX] > totalXMax) totalXMax = dimensions[edgesProcessed][X_MAX]; if (dimensions[edgesProcessed][Y_MIN] < totalYMin) totalYMin = dimensions[edgesProcessed][Y_MIN]; if (dimensions[edgesProcessed][Y_MAX] > totalYMax) totalYMax = dimensions[edgesProcessed][Y_MAX]; // Loop through all layers. // Search for lines overlapping with the topology line exactly. for (int i = 0; i < iGeoFeaturelayers.length; i++) { spatialFilter.setGeometryField(shapeFieldNames[i]); cursor = new FeatureCursor(iGeoFeaturelayers[i].search(spatialFilter, false)); feature = cursor.nextFeature(); // For each line, store presence. while (feature != null) { if (debug) System.out.println("feature of type " + iGeoFeaturelayers[i].getName()); overlaps[edgesProcessed][i]++; names[edgesProcessed] = addName(names[edgesProcessed], iGeoFeaturelayers[i].getFeatureClass().getAliasName()); feature = cursor.nextFeature(); } Cleaner.release(cursor); } if (names[edgesProcessed] == null) { names[edgesProcessed] = new String[1]; names[edgesProcessed][0] = "No edge"; } edgesProcessed++; iTopologyEdge = iEnumTopologyEdges.next(); } // End searching through topology. // 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, overlaps may contain different sets of layers. We'll work out the number of // times each set appears by first ordering the sets of overlap 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 // overlap numbers, not the names, // which are programmatically inert. The overlap numbers // represent the names, with, in this case, // the first number being the times LayerA appears as a overlap, // and the second column being the // number of times LayerB appears as a overlap for that edge. // First we convert the arrays of overlap numbers into Strings, // one per edge. String[] overlapsStrings = new String[edgeCount]; for (int i = 0; i < edgeCount; i++) { overlapsStrings[i] = ""; for (int j = 0; j < overlaps[i].length; j++) { overlapsStrings[i] = overlapsStrings[i] + overlaps[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 < edgeCount; 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 overlap numbers (in their String form), // dragging the names/lengths/dimensions into // order with them. Uses a simple bubble-sort. boolean swapped = true; int j = 0; String tempConnectionString = ""; String[] tempNameString = null; double[] tempDimensions = null; double tempLength = 0; while (swapped) { swapped = false; j++; for (int i = 0; i < overlapsStrings.length - j; i++) { if (overlapsStrings[i].compareTo(overlapsStrings[i + 1]) > 0) { tempConnectionString = overlapsStrings[i]; tempNameString = names[i]; tempLength = lengths[i]; tempDimensions = dimensions[i]; overlapsStrings[i] = overlapsStrings[i + 1]; names[i] = names[i + 1]; lengths[i] = lengths[i + 1]; dimensions[i] = dimensions[i + 1]; overlapsStrings[i + 1] = tempConnectionString; names[i + 1] = tempNameString; lengths[i + 1] = tempLength; dimensions[i + 1] = tempDimensions; swapped = true; } } } if (debug) { for (int i = 0; i < edgeCount; 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 overlap numbers, and // find out the number of times combinations arise. We record these // hits. Note we store these in arrays of size "edgeCount" 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[edgeCount]; String[] hitNames = new String[edgeCount]; double[] hitLengths = new double[edgeCount]; double[][] hitDimensions = new double[edgeCount][4]; double bigWidth = iTopologyGraph.getBuildExtent().getXMax(); double bigHeight = iTopologyGraph.getBuildExtent().getYMax(); // Initialise max/min dimensions to opposite, so they change on first check. for (int i = 0; i < edgeCount; i++) { hitNames[i] = ""; hitDimensions[i][X_MAX] = -1; hitDimensions[i][X_MIN] = bigWidth; hitDimensions[i][Y_MAX] = -1; hitDimensions[i][Y_MIN] = bigHeight; } for (int k = 0; k < names[0].length; k++) { hitNames[0] = hitNames[0] + " " + names[0][k]; } String currentString = overlapsStrings[0]; pb.setValue(80); // Count uniques and store associated layer names for (int i = 0; i < edgeCount; i++) { if (debug) System.out.println(overlapsStrings[i]); if (currentString.equals(overlapsStrings[i])) { countHits[hitCounter]++; hitLengths[hitCounter] = hitLengths[hitCounter] + lengths[i]; hitDimensions[hitCounter] = getDimensions(hitDimensions[hitCounter], dimensions[i]); } else { currentString = overlapsStrings[i]; hitCounter++; for (int k = 0; k < names[i].length; k++) { hitNames[hitCounter] = hitNames[hitCounter] + " " + names[i][k]; } countHits[hitCounter]++; hitLengths[hitCounter] = hitLengths[hitCounter] + lengths[i]; hitDimensions[hitCounter] = getDimensions(hitDimensions[hitCounter], dimensions[i]); } } pb.setValue(90); // Calc total area. double totalArea = Math.abs(totalXMax - totalXMin) * Math.abs(totalYMax - totalYMin); double totalCount = 0; double totalLength = 0; // Print results if (debug) { for (int i = 0; i < edgeCount; 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(".")) + "-OverlapStatistics-" + dateString + ".csv"; if (debug) System.out.println("Directory = " + filepath); FileWriter fw = new FileWriter(new File(filepath)); BufferedWriter bw = new BufferedWriter(fw); double area = 0; for (int i = 0; i < edgeCount; i++) { if (countHits[i] > 0) { totalCount = totalCount + countHits[i]; totalLength = totalLength + hitLengths[i]; System.out.println(hitDimensions[i][X_MAX] + " " + hitDimensions[i][X_MIN]+ " " + hitDimensions[i][Y_MAX] + " " + hitDimensions[i][Y_MIN]); area = (hitDimensions[i][X_MAX] - hitDimensions[i][X_MIN]) * (hitDimensions[i][Y_MAX] - hitDimensions[i][Y_MIN]); bw.write(countHits[i] + "," + hitNames[i] + "," + hitLengths[i] + "," + area); bw.newLine(); } } bw.write(totalCount + ",ALL_COMBINED," + totalLength + "," + totalArea); bw.newLine(); bw.flush(); bw.close(); dialog.dispose(); } catch (AutomationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Utility methods ------------------------------------------------------- /** * Method for calc min/max values in an array. Mins and maxs pulled out of two arrays and returned in the order * {min of array1[0] and array2[0], max of array1[0] and array2[0],min of array1[0] and array2[0], max of array1[0] and array2[0]}. * * @param array1 four-space array of mins and maxs. * @param array2 four-space array of mins and maxs. * @return array in order {min,max,min,max}. */ private double[] getDimensions(double[] array1, double[] array2) { double[] temp = new double[4]; temp[X_MIN] = (array1[X_MIN] < array2[X_MIN]) ? array1[X_MIN] : array2[X_MIN]; temp[X_MAX] = (array1[X_MAX] > array2[X_MAX]) ? array1[X_MAX] : array2[X_MAX]; temp[Y_MIN] = (array1[Y_MIN] < array2[Y_MIN]) ? array1[Y_MIN] : array2[Y_MIN]; temp[Y_MAX] = (array1[Y_MAX] > array2[Y_MAX]) ? array1[Y_MAX] : array2[Y_MAX]; return temp; } /** * 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; } } // End of class }