/** * AllLineTypeDistances.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.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.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.interop.AutomationException; 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 distance statistics for each boundary type against each other. * Distance is distance between each polylines and its nearest (minimum) or furthest (maximum) neighbour of each other type. * Reports Type, number of polylines of that type, average minimum distance, standard deviation of the population of minimums, * average distance, standard deviation of the population of distances, * average maximum distance, standard deviation of the population of maximums. * * @author Andrew Evans * @version 0.1 */ public class AllLineTypeDistances 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(); allLineTypeDistances(); } /** * 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, "All Line Type Distances"); dialog.getContentPane().add(center_panel, BorderLayout.CENTER); dialog.pack(); dialog.setVisible(true); dialog.setLocationRelativeTo(null); // center on screen dialog.toFront(); // raise above other java windows } /** * Method for listing statistics associated with polylines. * Calculates min and max distance statistics for each boundary type. * Distance is distance between each polylines and its nearest (minimum) or furthest (maximum) neighbour of the same type. * Reports Type, number of polylines of that type, average minimum distance, standard deviation of the population of minimums, * average distance, standard deviation of the population of distances, * average maximum distance, standard deviation of the population of maximums. * @todo Need to think about double counting and statistics - unlikely to skew minimums, as minimum distances can be less for neighbour, but maximums? */ private void allLineTypeDistances() { try { pb.setValue(0); // Init necessary storage variables. if (debug) System.out.println("Starting allLineTypeDistances analysis"); IFeature feature = null; IQueryFilter queryFilter = new QueryFilter(); IFeatureCursor cursor = null; IFeatureCursor neighbourCursor = null; IFeature neighbour = null; String[] names = new String[iGeoFeaturelayers.length]; int[] n = new int[iGeoFeaturelayers.length]; double[][] totalDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][] totalSquaredDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][][] minDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length][]; double[][][] maxDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length][]; double possibleMax = iTopologyGraph.getBuildExtent().getWidth(); double height = iTopologyGraph.getBuildExtent().getHeight(); if (height > possibleMax) { possibleMax = height; } double minDistance = possibleMax; double maxDistance = 0; double distance = 0; int featureCount = 0; if (debug) System.out.println(""); if (debug) System.out.println("Initial: min = " + minDistance + " max = " + maxDistance); if (debug) System.out.println("iGeoFeaturelayers.length = " + iGeoFeaturelayers.length); for (int i = 0; i < iGeoFeaturelayers.length; i++) { n[i] = ((IAttributeTable)iGeoFeaturelayers[i]).getAttributeTable().rowCount(queryFilter); names[i] = iGeoFeaturelayers[i].getFeatureClass().getAliasName(); } // Loop through each boundary type. for (int i = 0; i < iGeoFeaturelayers.length; i++) { pb.setValue((int) ((100.0 / iGeoFeaturelayers.length) * i)); // Loop through first type features. for (int j = 0; j < iGeoFeaturelayers.length; j++) { // Get first type features. cursor = iGeoFeaturelayers[i].search(queryFilter, false); // For each other feature type, build arrays to hold data about the relationships with type i. minDistances[i][j] = new double[n[i]]; maxDistances[i][j] = new double[n[i]]; totalDistances[i][j] = 0; totalSquaredDistances[i][j] = 0; // Get first i feature. featureCount = 0; feature = cursor.nextFeature(); if (debug) System.out.println("Processing " + iGeoFeaturelayers[i].getFeatureClass().getAliasName()); // Loop through each i feature. while (feature != null) { // Find all j features. neighbourCursor = iGeoFeaturelayers[j].search(queryFilter, false); // Get first j feature. neighbour = neighbourCursor.nextFeature(); minDistance = possibleMax; maxDistance = 0; distance = 0; // if (debug) System.out.println("Working neighbours"); // For each i feature, loop through all the j features // working out the stats. // Note that as i and j could be the same we check whether the i and j features are the same. while (neighbour != null) { if (neighbour.getOID() != feature.getOID()) { distance = ((Polyline) (feature.getShape())).returnDistance(neighbour.getShape()); totalDistances[i][j] = totalDistances[i][j] + distance; totalSquaredDistances[i][j] = totalSquaredDistances[i][j] + (distance * distance); if (distance < minDistance) { minDistance = distance; } if (distance > maxDistance) { maxDistance = distance; } } // if (debug) System.out.println("Distance " + // distance); // Get next j. neighbour = neighbourCursor.nextFeature(); } // End looping through j features. // Set min and max distance to the i feature. minDistances[i][j][featureCount] = minDistance; maxDistances[i][j][featureCount] = maxDistance; featureCount++; // Next i feature. feature = cursor.nextFeature(); } // End looping through i features. } // End looping through j layers. } // End looping through i layers. // Calculate stats. double[][] averageMinimumDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][] standardDeviationMinimumDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][] averageDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][] standardDeviationDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][] averageMaximumDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double[][] standardDeviationMaximumDistances = new double[iGeoFeaturelayers.length][iGeoFeaturelayers.length]; double minSum = 0; double averageSum = 0; double maxSum = 0; for (int i = 0; i < iGeoFeaturelayers.length; i++) { for (int j = 0; j < iGeoFeaturelayers.length; j++) { minSum = 0; averageSum = 0; maxSum = 0; for (int k = 0; k < minDistances[i][j].length; k++) { minSum = minSum + minDistances[i][j][k]; maxSum = maxSum + maxDistances[i][j][k]; } averageMinimumDistances[i][j] = minSum/minDistances[i][j].length; if (i != j) { averageDistances[i][j] = totalDistances[i][j] / (n[i] * n[j]); } else { averageDistances[i][j] = totalDistances[i][j] / (n[i] * (n[j] - 1)); } averageMaximumDistances[i][j] = maxSum/maxDistances[i][j].length; minSum = 0; maxSum = 0; for (int k = 0; k < minDistances[i][j].length; k++) { minSum = minSum + ((minDistances[i][j][k] - averageMinimumDistances[i][j]) * (minDistances[i][j][k] - averageMinimumDistances[i][j])); maxSum = maxSum + ((maxDistances[i][j][k] - averageMaximumDistances[i][j]) * (maxDistances[i][j][k] - averageMaximumDistances[i][j])); } standardDeviationMinimumDistances[i][j] = Math.pow(minSum / minDistances[i][j].length, 0.5); if (i != j) { standardDeviationDistances[i][j] = Math.pow((totalSquaredDistances[i][j] / (n[i] * n[j])) - (averageDistances[i][j] * averageDistances[i][j]) , 0.5); } else { standardDeviationDistances[i][j] = Math.pow((totalSquaredDistances[i][j] / (n[i] * (n[j] - 1))) - (averageDistances[i][j] * averageDistances[i][j]) , 0.5); } standardDeviationMaximumDistances[i][j] = Math.pow(maxSum / maxDistances[i][j].length, 0.5); } } // Print results. if (debug) { for (int i = 0; i < iGeoFeaturelayers.length; i++) { for (int j = 0; j < iGeoFeaturelayers.length; j++) { System.out.print(names[i] + " n = " + n[i] + names[j] + " n = " + n[j]); System.out.print(" averageMin = " + averageMinimumDistances[i][j] + " SDMins = " + standardDeviationMinimumDistances[i][j]); System.out.print(" averageDistance = " + averageDistances[i][j] + " SD = " + standardDeviationDistances[i][j]); System.out.println(" averageMax = " + averageMaximumDistances[i][j] + " SDMaxs = " + standardDeviationMaximumDistances[i][j]); } } } // Write results File directory = ChangeOutputDirectory.getInstance().getDirectory(); if (debug) System.out.println("Proposed Directory = " + directory); 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(".")) + "-LineTypeDistances-" + dateString + ".csv"; if (debug) System.out.println("Directory = " + filepath); FileWriter fw = new FileWriter(new File (filepath)); BufferedWriter bw = new BufferedWriter(fw); bw.write("Type,n,averageMin,SDMins,averageDistance,SDDistances,averageMax,SDMaxs"); bw.newLine(); for (int i = 0; i < iGeoFeaturelayers.length; i++) { for (int j = 0; j < iGeoFeaturelayers.length; j++) { bw.write(names[i] + "," + n[i] + "," + names[j] + "," + n[j] + ","); bw.write(averageMinimumDistances[i][j] + "," + standardDeviationMinimumDistances[i][j] + ","); bw.write(averageDistances[i][j] + "," + standardDeviationDistances[i][j] + ","); bw.write(averageMaximumDistances[i][j] + "," + standardDeviationMaximumDistances[i][j]); bw.newLine(); } } // Write tables. bw.newLine(); bw.newLine(); bw.newLine(); writeTable("averageMin", averageMinimumDistances, names, bw); bw.newLine(); bw.newLine(); bw.newLine(); writeTable("SDMins", standardDeviationMinimumDistances, names, bw); bw.newLine(); bw.newLine(); bw.newLine(); writeTable("averageDistance", averageDistances, names, bw); bw.newLine(); bw.newLine(); bw.newLine(); writeTable("SDDistances", standardDeviationDistances, names, bw); bw.newLine(); bw.newLine(); bw.newLine(); writeTable("averageMax", averageMaximumDistances, names, bw); bw.newLine(); bw.newLine(); bw.newLine(); writeTable("SDMaxs", standardDeviationMaximumDistances, names, bw); bw.flush(); bw.close(); dialog.dispose(); } catch (AutomationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void writeTable(String title, double[][] data, String[] names, BufferedWriter bw) { try { for (int i = 0; i < iGeoFeaturelayers.length - 1; i++) { bw.write(title + ","); } bw.write(title); bw.newLine(); for (int i = 0; i < iGeoFeaturelayers.length - 1; i++) { bw.write(names[i] + ","); } bw.write(names[iGeoFeaturelayers.length - 1]); bw.newLine(); for (int i = 0; i < iGeoFeaturelayers.length; i++) { bw.write(names[i] + ","); for (int j = 0; j < iGeoFeaturelayers.length - 1; j++) { bw.write(data[i][j] + ","); } bw.write(data[i][iGeoFeaturelayers.length - 1] + ""); bw.newLine(); } } catch (IOException ioe) { ioe.printStackTrace(); } } // End of class }