/** * LineTypeDistances.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. * 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. * * @author Andrew Evans * @version 0.1 */ public class LineTypeDistances 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(); lineTypeDistances(); } /** * 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, "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 lineTypeDistances() { try { pb.setValue(0); // Init necessary storage variables. if (debug) System.out.println("Starting lineTypeDistances 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]; double[] totalSquaredDistances = new double[iGeoFeaturelayers.length]; double[][] minDistances = new double[iGeoFeaturelayers.length][]; double[][] maxDistances = new double[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); // Loop through each boundary type. for (int i = 0; i < iGeoFeaturelayers.length; i++) { // Run through the features in each boundary type. // XXXX Do we want polylines or edges. At the moment polylines are used. pb.setValue((int)((100.0 / iGeoFeaturelayers.length) * i)); cursor = iGeoFeaturelayers[i].search (queryFilter, false); n[i] = ((IAttributeTable)iGeoFeaturelayers[i]).getAttributeTable().rowCount(queryFilter); minDistances[i] = new double[n[i]]; maxDistances[i] = new double[n[i]]; totalDistances[i] = 0; totalSquaredDistances[i] = 0; featureCount = 0; feature = cursor.nextFeature(); if (debug) System.out.println("Processing " + iGeoFeaturelayers[i].getFeatureClass().getAliasName()); while (feature != null) { neighbourCursor = iGeoFeaturelayers[i].search (queryFilter, false); neighbour = neighbourCursor.nextFeature(); minDistance = possibleMax; maxDistance = 0; distance = 0; //if (debug) System.out.println("Working neighbours"); // For each feature, loop through all the other features working out the stats. while (neighbour != null) { if (neighbour.getOID() != feature.getOID()) { distance = ((Polyline)(feature.getShape())).returnDistance(neighbour.getShape()); totalDistances[i] = totalDistances[i] + distance; totalSquaredDistances[i] = totalSquaredDistances[i] + (distance * distance); if (distance < minDistance) { minDistance = distance; } if (distance > maxDistance) { maxDistance = distance; } } //if (debug) System.out.println("Distance " + distance); neighbour = neighbourCursor.nextFeature(); } minDistances[i][featureCount] = minDistance; maxDistances[i][featureCount] = maxDistance; featureCount++; feature = cursor.nextFeature(); } names[i] = iGeoFeaturelayers[i].getFeatureClass().getAliasName(); } // Calculate stats. double[] averageMinimumDistances = new double[iGeoFeaturelayers.length]; double[] standardDeviationMinimumDistances = new double[iGeoFeaturelayers.length]; double[] averageDistances = new double[iGeoFeaturelayers.length]; double[] standardDeviationDistances = new double[iGeoFeaturelayers.length]; double[] averageMaximumDistances = new double[iGeoFeaturelayers.length]; double[] standardDeviationMaximumDistances = new double[iGeoFeaturelayers.length]; double minSum = 0; double averageSum = 0; double maxSum = 0; for (int i = 0; i < iGeoFeaturelayers.length; i++) { minSum = 0; averageSum = 0; maxSum = 0; for (int j = 0; j < minDistances[i].length; j++) { minSum = minSum + minDistances[i][j]; maxSum = maxSum + maxDistances[i][j]; } averageMinimumDistances[i] = minSum/minDistances[i].length; averageDistances[i] = totalDistances[i] / (n[i] * (n[i] - 1)); averageMaximumDistances[i] = maxSum/maxDistances[i].length; minSum = 0; maxSum = 0; for (int j = 0; j < minDistances[i].length; j++) { minSum = minSum + ((minDistances[i][j] - averageMinimumDistances[i]) * (minDistances[i][j] - averageMinimumDistances[i])); maxSum = maxSum + ((maxDistances[i][j] - averageMaximumDistances[i]) * (maxDistances[i][j] - averageMaximumDistances[i])); } standardDeviationMinimumDistances[i] = Math.pow(minSum / minDistances[i].length, 0.5); standardDeviationDistances[i] = Math.pow((totalSquaredDistances[i] / (n[i] * (n[i] - 1)) ) - (averageDistances[i] * averageDistances[i]) , 0.5); standardDeviationMaximumDistances[i] = Math.pow(maxSum / maxDistances[i].length, 0.5); } // Print results. if (debug) { for (int i = 0; i < iGeoFeaturelayers.length; i++) { System.out.print(names[i] + " n = " + n[i]); System.out.print(" averageMin = " + averageMinimumDistances[i] + " SDMins = " + standardDeviationMinimumDistances[i]); System.out.print(" averageDistance = " + averageDistances[i] + " SD = " + standardDeviationDistances[i]); System.out.println(" averageMax = " + averageMaximumDistances[i] + " SDMaxs = " + standardDeviationMaximumDistances[i]); } } // 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++) { bw.write(names[i] + "," + n[i] + ","); bw.write(averageMinimumDistances[i] + "," + standardDeviationMinimumDistances[i] + ","); bw.write(averageDistances[i] + "," + standardDeviationDistances[i] + ","); bw.write(averageMaximumDistances[i] + "," + standardDeviationMaximumDistances[i]); bw.newLine(); } bw.flush(); bw.close(); dialog.dispose(); } catch (AutomationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // End of class }