def __init__(self):
        self.label = "Create Random Points on a Network Dataset"
        self.description = "Create a series of random points on a network dataset."
        self.canRunInBackground = False

        arcpy.env.overwriteOutput = True
        self.kfHelper = KFunctionHelper()
  def __init__(self):
    self.label = "Create Random Points on a Network Dataset"
    self.description = "Create a series of random points on a network dataset."
    self.canRunInBackground = False

    arcpy.env.overwriteOutput = True
    self.kfHelper = KFunctionHelper()
 def __init__(self):
     self.kfHelper = KFunctionHelper()
 def __init__(self):
   self.label              = "Global K Function"
   self.description        = "Uses a Global Network K Function to analyze clustering and dispersion trends in a set of crash points."
   self.canRunInBackground = False
   env.overwriteOutput     = True
   self.kfHelper           = KFunctionHelper()
class GlobalKFunction(object):
  ###
  # Initialize the tool.
  ###
  def __init__(self):
    self.label              = "Global K Function"
    self.description        = "Uses a Global Network K Function to analyze clustering and dispersion trends in a set of crash points."
    self.canRunInBackground = False
    env.overwriteOutput     = True
    self.kfHelper           = KFunctionHelper()
  
  ###
  # Get input from the users.
  ###
  def getParameterInfo(self):
    # Input origin features.
    points = arcpy.Parameter(
      displayName="Input Points Feature Dataset",
      name="points",
      datatype="Feature Class",
      parameterType="Required",
      direction="Input")
    points.filter.list = ["Point"]

    # Network dataset.
    networkDataset = arcpy.Parameter(
      displayName="Input Network Dataset",
      name = "network_dataset",
      datatype="Network Dataset Layer",
      parameterType="Required",
      direction="Input")

    # Number of distance increments.
    numBands = arcpy.Parameter(
      displayName="Input Number of Distance Bands",
      name="num_dist_bands",
      datatype="Long",
      parameterType="Optional",
      direction="Input")

    # Beginning distance.
    begDist = arcpy.Parameter(
      displayName="Input Beginning Distance",
      name="beginning_distance",
      datatype="Double",
      parameterType="Required",
      direction="Input")
    begDist.value = 0

    # Distance increment.
    distInc = arcpy.Parameter(
      displayName="Input Distance Increment",
      name="distance_increment",
      datatype="Double",
      parameterType="Required",
      direction="Input")
    distInc.value = 1000

    # Snap distance.
    snapDist = arcpy.Parameter(
      displayName="Input Snap Distance",
      name="snap_distance",
      datatype="Double",
      parameterType="Required",
      direction="Input")
    snapDist.value = 25

    # Output location.
    outNetKLoc = arcpy.Parameter(
      displayName="Output Location (Database Path)",
      name="out_location",
      datatype="DEWorkspace",
      parameterType="Required",
      direction="Input")
    outNetKLoc.value = arcpy.env.workspace

    # The raw ODCM data.
    outRawODCMFCName = arcpy.Parameter(
      displayName="Raw ODCM Data Table",
      name = "output_raw_odcm_feature_class",
      datatype="GPString",
      parameterType="Required",
      direction="Output")
    outRawODCMFCName.value = "Global_K_Raw_ODCM_Data"

    # The raw data feature class (e.g. observed and random point computations).
    outRawFCName = arcpy.Parameter(
      displayName="Raw Global-K Data Table (Raw Analysis Data)",
      name = "output_raw_analysis_feature_class",
      datatype="GPString",
      parameterType="Required",
      direction="Output")
    outRawFCName.value = "Global_K_Raw_Analysis_Data"

    # The analysis feature class.
    outAnlFCName = arcpy.Parameter(
      displayName="Global-K Summary Data (Plottable Data)",
      name = "output_analysis_feature_class",
      datatype="GPString",
      parameterType="Required",
      direction="Output")
    outAnlFCName.value = "Global_K_Summary_Data"

    # Confidence envelope (number of permutations).
    numPerms = arcpy.Parameter(
      displayName="Number of Random Point Permutations",
      name = "num_permutations",
      datatype="GPString",
      parameterType="Required",
      direction="Input")
    permKeys             = self.kfHelper.getPermutationSelection().keys()
    numPerms.filter.list = permKeys
    numPerms.value       = permKeys[0]

    # Projected coordinate system.
    outCoordSys = arcpy.Parameter(
      displayName="Output Network Dataset Length Projected Coordinate System",
      name="coordinate_system",
      datatype="GPSpatialReference",
      parameterType="Optional",
      direction="Input")
 
    # Number of points field.
    numPointsFieldName = arcpy.Parameter(
      displayName="Number of Points Field",
      name = "num_points_field",
      datatype="GPString",
      parameterType="Optional",
      direction="Input")
   
    return [points, networkDataset, numBands, begDist, distInc, snapDist,
      outNetKLoc, outRawODCMFCName, outRawFCName, outAnlFCName, numPerms,
      outCoordSys, numPointsFieldName]

  ###
  # Check if the tool is available for use.
  ###
  def isLicensed(self):
    # Network Analyst tools must be available.
    return arcpy.CheckExtension("Network") == "Available"

  ###
  # Set parameter defaults.
  ###
  def updateParameters(self, parameters):
    networkDataset = parameters[1].value
    outCoordSys    = parameters[11].value

    # Default the coordinate system.
    if networkDataset is not None and outCoordSys is None:
      ndDesc = arcpy.Describe(networkDataset)
      # If the network dataset's coordinate system is a projected one,
      # use its coordinate system as the defualt.
      if (ndDesc.spatialReference.projectionName != "" and
        ndDesc.spatialReference.linearUnitName == "Meter" and
        ndDesc.spatialReference.factoryCode != 0):
        parameters[11].value = ndDesc.spatialReference.factoryCode

    # Set the source of the fields (the network dataset).
    if networkDataset is not None:
      parameters[12].filter.list = self.kfHelper.getEdgeSourceFieldNames(networkDataset)

  ###
  # If any fields are invalid, show an appropriate error message.
  ###
  def updateMessages(self, parameters):
    outCoordSys = parameters[11].value

    if outCoordSys is not None:
      if outCoordSys.projectionName == "":
        parameters[11].setErrorMessage("Output coordinate system must be a projected coordinate system.")
      elif outCoordSys.linearUnitName != "Meter":
        parameters[11].setErrorMessage("Output coordinate system must have a linear unit code of 'Meter.'")
      else:
        parameters[11].clearMessage()

  ###
  # Execute the tool.
  ###
  def execute(self, parameters, messages):
    points             = parameters[0].valueAsText
    networkDataset     = parameters[1].valueAsText
    numBands           = parameters[2].value
    begDist            = parameters[3].value
    distInc            = parameters[4].value
    snapDist           = parameters[5].value
    outNetKLoc         = parameters[6].valueAsText
    outRawODCMFCName   = parameters[7].valueAsText
    outRawFCName       = parameters[8].valueAsText
    outAnlFCName       = parameters[9].valueAsText
    numPermsDesc       = parameters[10].valueAsText
    numPerms           = self.kfHelper.getPermutationSelection()[numPermsDesc]
    outCoordSys        = parameters[11].value
    numPointsFieldName = parameters[12].value
    ndDesc             = arcpy.Describe(networkDataset)
    gkfSvc             = GlobalKFunctionSvc()

    # Refer to the note in the NetworkDatasetLength tool.
    if outCoordSys is None:
      outCoordSys = ndDesc.spatialReference

    messages.addMessage("\nOrigin points: {0}".format(points))
    messages.addMessage("Network dataset: {0}".format(networkDataset))
    messages.addMessage("Number of distance bands: {0}".format(numBands))
    messages.addMessage("Beginning distance: {0}".format(begDist))
    messages.addMessage("Distance increment: {0}".format(distInc))
    messages.addMessage("Snap distance: {0}".format(snapDist))
    messages.addMessage("Output location (database path): {0}".format(outNetKLoc))
    messages.addMessage("Raw ODCM data table: {0}".format(outRawODCMFCName))
    messages.addMessage("Raw global-K data table (raw analysis data): {0}".format(outRawFCName))
    messages.addMessage("Global-K summary data (plottable data): {0}".format(outAnlFCName))
    messages.addMessage("Number of random permutations: {0}".format(numPerms))
    messages.addMessage("Network dataset length projected coordinate system: {0}".format(outCoordSys.name))
    messages.addMessage("Number of Points Field Name: {0}\n".format(numPointsFieldName))

    # Calculate the length of the network.
    networkLength = self.kfHelper.calculateLength(networkDataset, outCoordSys)
    messages.addMessage("Total network length: {0}".format(networkLength))

    # Count the number of crashes.
    numPoints = self.kfHelper.countNumberOfFeatures(os.path.join(outNetKLoc, points))

    # Set up a cutoff lenght for the ODCM data if possible.  (Optimization.)
    cutoff = gkfSvc.getCutoff(numBands, distInc, begDist)

    # The results of all the calculations end up here.
    netKCalculations = []

    # Use a mutable container for the number of bands so that the below callback
    # can write to it.  The "nonlocal" keyword not available in Python 2.x.
    numBandsCont = [numBands]

    # Callback function that does the Network K calculation on an OD cost matrix.    
    def doNetKCalc(odDists, iteration):
      # Do the actual network k-function calculation.
      netKCalc = NetworkKCalculation(networkLength, numPoints, odDists, begDist, distInc, numBandsCont[0])
      netKCalculations.append(netKCalc.getDistanceBands())

      # If the user did not specifiy a number of distance bands explicitly,
      # store the number of bands.  It's computed from the observed data.
      if numBandsCont[0] is None:
        numBandsCont[0] = netKCalc.getNumberOfDistanceBands()

    # Generate the ODCM permutations, including the ODCM for the observed data.
    # doNetKCalc is called on each iteration.
    randODCMPermSvc = RandomODCMPermutationsSvc()
    randODCMPermSvc.generateODCMPermutations("Global Analysis",
      points, points, networkDataset, snapDist, cutoff, outNetKLoc,
      outRawODCMFCName, numPerms, outCoordSys, numPointsFieldName, messages, doNetKCalc)

    # Store the raw analysis data.
    messages.addMessage("Writing raw analysis data.")
    gkfSvc.writeRawAnalysisData(outNetKLoc, outRawFCName, netKCalculations)

    # Analyze the data and store the results.
    messages.addMessage("Analyzing data.")
    gkfSvc.writeAnalysisSummaryData(numPerms, netKCalculations, outNetKLoc, outAnlFCName)
 def __init__(self):
   self.label              = "Cross K Function"
   self.description        = "Uses a Cross K Function to analyze clustering and dispersion trends in a set of origin and destination points (for example, bridges and crashes)."
   self.canRunInBackground = False
   env.overwriteOutput     = True
   self.kfHelper           = KFunctionHelper()
Example #7
0
 def __init__(self):
   self.label              = "Random ODCM Permutations"
   self.description        = "Generates OD Cost Matrices with oberved data and a complementrary set of random point permutations."
   self.canRunInBackground = False
   env.overwriteOutput     = True
   self.kfHelper           = KFunctionHelper()
Example #8
0
class RandomODCMPermutations(object):
  ###
  # Initialize the tool.
  ###
  def __init__(self):
    self.label              = "Random ODCM Permutations"
    self.description        = "Generates OD Cost Matrices with oberved data and a complementrary set of random point permutations."
    self.canRunInBackground = False
    env.overwriteOutput     = True
    self.kfHelper           = KFunctionHelper()
  
  ###
  # Get input from the users.
  ###
  def getParameterInfo(self):
    # Analysis type.
    analysisType = arcpy.Parameter(
      displayName="Analysis Type",
      name = "analysis_type",
      datatype="GPString",
      parameterType="Required",
      direction="Input")
    atKeys                   = self.kfHelper.getAnalysisTypeSelection().keys()
    analysisType.filter.list = atKeys
    analysisType.value       = atKeys[0]

    # Input origin points features.
    srcPoints = arcpy.Parameter(
      displayName="Input Origin Points Feature Dataset (e.g. bridges)",
      name="srcPoints",
      datatype="Feature Class",
      parameterType="Required",
      direction="Input")
    srcPoints.filter.list = ["Point"]

    # Input destination origin features.
    destPoints = arcpy.Parameter(
      displayName="Input Destination Points Feature Dataset (e.g. crashes)",
      name="destPoints",
      datatype="Feature Class",
      parameterType="Required",
      direction="Input")
    destPoints.filter.list = ["Point"]

    # Network dataset.
    networkDataset = arcpy.Parameter(
      displayName="Input Network Dataset",
      name = "network_dataset",
      datatype="Network Dataset Layer",
      parameterType="Required",
      direction="Input")

    # Snap distance.
    snapDist = arcpy.Parameter(
      displayName="Input Snap Distance",
      name="snap_distance",
      datatype="Double",
      parameterType="Required",
      direction="Input")
    snapDist.value = 25

    # Cutoff distance.
    cutoff = arcpy.Parameter(
      displayName="Input Cutoff Distance",
      name="cutoff_distance",
      datatype="Double",
      parameterType="Optional",
      direction="Input")

    # Output location.
    outLoc = arcpy.Parameter(
      displayName="Output Location (Database Path)",
      name="out_location",
      datatype="DEWorkspace",
      parameterType="Required",
      direction="Input")
    outLoc.value = arcpy.env.workspace

    # Raw data feature class.
    outFC = arcpy.Parameter(
      displayName="Output Feature Class Name (Raw ODCM Data)",
      name = "output_raw_feature_class",
      datatype="GPString",
      parameterType="Required",
      direction="Output")
    outFC.value = "ODCM_Raw_Data"

    # Confidence envelope (number of permutations).
    numPerms = arcpy.Parameter(
      displayName="Number of Random Point Permutations",
      name = "num_permutations",
      datatype="GPString",
      parameterType="Required",
      direction="Input")
    permKeys             = self.kfHelper.getPermutationSelection().keys()
    numPerms.filter.list = permKeys
    numPerms.value       = permKeys[0]

    # Projected coordinate system.
    outCoordSys = arcpy.Parameter(
      displayName="Output Network Dataset Length Projected Coordinate System",
      name="coordinate_system",
      datatype="GPSpatialReference",
      parameterType="Optional",
      direction="Input")

    # Number of points field.
    numPointsFieldName = arcpy.Parameter(
      displayName="Number of Points Field",
      name = "num_points_field",
      datatype="GPString",
      parameterType="Optional",
      direction="Input")
   
    return [analysisType, srcPoints, destPoints, networkDataset, snapDist,
      cutoff, outLoc, outFC, numPerms, outCoordSys, numPointsFieldName]

  ###
  # Check if the tool is available for use.
  ###
  def isLicensed(self):
    # Network Analyst tools must be available.
    return arcpy.CheckExtension("Network") == "Available"

  ###
  # Set parameter defaults.
  ###
  def updateParameters(self, parameters):
    networkDataset = parameters[3].value
    outCoordSys    = parameters[9].value

    # Default the coordinate system.
    if networkDataset is not None and outCoordSys is None:
      ndDesc = arcpy.Describe(networkDataset)
      # If the network dataset's coordinate system is a projected one,
      # use its coordinate system as the defualt.
      if (ndDesc.spatialReference.projectionName != "" and
        ndDesc.spatialReference.linearUnitName == "Meter" and
        ndDesc.spatialReference.factoryCode != 0):
        parameters[9].value = ndDesc.spatialReference.factoryCode

    # Enable/disable the destination points based on the analysis type.  CROSS has
    # sources and destinations; GLOBAL only has one set of points.
    analysisTypes = self.kfHelper.getAnalysisTypeSelection()
    if parameters[0].valueAsText in analysisTypes:
      if analysisTypes[parameters[0].valueAsText] == "CROSS":
        parameters[2].enabled = True
      else:
        parameters[2].enabled = False
        parameters[2].value = parameters[1].valueAsText

    # Set the source of the fields (the network dataset).
    if networkDataset is not None:
      parameters[10].filter.list = self.kfHelper.getEdgeSourceFieldNames(networkDataset)

  ###
  # If any fields are invalid, show an appropriate error message.
  ###
  def updateMessages(self, parameters):
    outCoordSys = parameters[9].value

    if outCoordSys is not None:
      if outCoordSys.projectionName == "":
        parameters[9].setErrorMessage("Output coordinate system must be a projected coordinate system.")
      elif outCoordSys.linearUnitName != "Meter":
        parameters[9].setErrorMessage("Output coordinate system must have a linear unit code of 'Meter.'")
      else:
        parameters[9].clearMessage()

  ###
  # Execute the tool.
  ###
  def execute(self, parameters, messages):
    analysisType       = self.kfHelper.getAnalysisTypeSelection()[parameters[0].valueAsText]
    srcPoints          = parameters[1].valueAsText
    destPoints         = parameters[2].valueAsText
    networkDataset     = parameters[3].valueAsText
    snapDist           = parameters[4].value
    cutoff             = parameters[5].value
    outLoc             = parameters[6].valueAsText
    outFC              = parameters[7].valueAsText
    numPerms           = self.kfHelper.getPermutationSelection()[parameters[8].valueAsText]
    outCoordSys        = parameters[9].value
    numPointsFieldName = parameters[10].value
    ndDesc             = arcpy.Describe(networkDataset)

    # Refer to the note in the NetworkDatasetLength tool.
    if outCoordSys is None:
      outCoordSys = ndDesc.spatialReference

    messages.addMessage("\nAnalysis type: {0}".format(analysisType))
    messages.addMessage("Origin points: {0}".format(srcPoints))
    messages.addMessage("Destination points: {0}".format(destPoints))
    messages.addMessage("Network dataset: {0}".format(networkDataset))
    messages.addMessage("Snap distance: {0}".format(snapDist))
    messages.addMessage("Cutoff distance: {0}".format(cutoff))
    messages.addMessage("Path to output feature class: {0}".format(outLoc))
    messages.addMessage("Output feature class name: {0}".format(outFC))
    messages.addMessage("Number of random permutations: {0}".format(numPerms))
    messages.addMessage("Network dataset length projected coordinate system: {0}".format(outCoordSys.name))
    messages.addMessage("Number of Points Field Name: {0}\n".format(numPointsFieldName))

    # The actual work is done in a reusable service.
    randODCMPermSvc = RandomODCMPermutationsSvc()
    randODCMPermSvc.generateODCMPermutations(analysisType, srcPoints, destPoints,
      networkDataset, snapDist, cutoff, outLoc, outFC, numPerms, outCoordSys,
      numPointsFieldName, messages)
Example #9
0
 def __init__(self):
     self.label = "Cross K Function"
     self.description = "Uses a Cross K Function to analyze clustering and dispersion trends in a set of origin and destination points (for example, bridges and crashes)."
     self.canRunInBackground = False
     env.overwriteOutput = True
     self.kfHelper = KFunctionHelper()
Example #10
0
class CrossKFunction(object):
    ###
    # Initialize the tool.
    ###
    def __init__(self):
        self.label = "Cross K Function"
        self.description = "Uses a Cross K Function to analyze clustering and dispersion trends in a set of origin and destination points (for example, bridges and crashes)."
        self.canRunInBackground = False
        env.overwriteOutput = True
        self.kfHelper = KFunctionHelper()

    ###
    # Get input from the users.
    ###
    def getParameterInfo(self):
        # Input origin points features.
        srcPoints = arcpy.Parameter(
            displayName="Input Origin Points Feature Dataset (e.g. bridges)",
            name="srcPoints",
            datatype="Feature Class",
            parameterType="Required",
            direction="Input")
        srcPoints.filter.list = ["Point"]

        # Input destination origin features.
        destPoints = arcpy.Parameter(
            displayName=
            "Input Destination Points Feature Dataset (e.g. crashes)",
            name="destPoints",
            datatype="Feature Class",
            parameterType="Required",
            direction="Input")
        destPoints.filter.list = ["Point"]

        # Network dataset.
        networkDataset = arcpy.Parameter(displayName="Input Network Dataset",
                                         name="network_dataset",
                                         datatype="Network Dataset Layer",
                                         parameterType="Required",
                                         direction="Input")

        # Number of distance increments.
        numBands = arcpy.Parameter(
            displayName="Input Number of Distance Bands",
            name="num_dist_bands",
            datatype="Long",
            parameterType="Optional",
            direction="Input")

        # Beginning distance.
        begDist = arcpy.Parameter(displayName="Input Beginning Distance",
                                  name="beginning_distance",
                                  datatype="Double",
                                  parameterType="Required",
                                  direction="Input")
        begDist.value = 0

        # Distance increment.
        distInc = arcpy.Parameter(displayName="Input Distance Increment",
                                  name="distance_increment",
                                  datatype="Double",
                                  parameterType="Required",
                                  direction="Input")
        distInc.value = 1000

        # Snap distance.
        snapDist = arcpy.Parameter(displayName="Input Snap Distance",
                                   name="snap_distance",
                                   datatype="Double",
                                   parameterType="Required",
                                   direction="Input")
        snapDist.value = 25

        # Output location.
        outNetKLoc = arcpy.Parameter(
            displayName="Output Location (Database Path)",
            name="out_location",
            datatype="DEWorkspace",
            parameterType="Required",
            direction="Input")
        outNetKLoc.value = arcpy.env.workspace

        # The raw ODCM data.
        outRawODCMFCName = arcpy.Parameter(
            displayName="Raw ODCM Data Table",
            name="output_raw_odcm_feature_class",
            datatype="GPString",
            parameterType="Required",
            direction="Output")
        outRawODCMFCName.value = "Cross_K_Raw_ODCM_Data"

        # The raw data feature class (e.g. observed and random point computations).
        outRawFCName = arcpy.Parameter(
            displayName="Raw Network-K Data Table (Raw Analysis Data)",
            name="output_raw_analysis_feature_class",
            datatype="GPString",
            parameterType="Required",
            direction="Output")
        outRawFCName.value = "Cross_K_Raw_Analysis_Data"

        # The analysis feature class.
        outAnlFCName = arcpy.Parameter(
            displayName="Network-K Summary Data (Plottable Data)",
            name="output_analysis_feature_class",
            datatype="GPString",
            parameterType="Required",
            direction="Output")
        outAnlFCName.value = "Cross_K_Summary_Data"

        # Confidence envelope (number of permutations).
        numPerms = arcpy.Parameter(
            displayName="Number of Random Point Permutations",
            name="num_permutations",
            datatype="GPString",
            parameterType="Required",
            direction="Input")
        permKeys = self.kfHelper.getPermutationSelection().keys()
        numPerms.filter.list = permKeys
        numPerms.value = permKeys[0]

        # Projected coordinate system.
        outCoordSys = arcpy.Parameter(
            displayName=
            "Output Network Dataset Length Projected Coordinate System",
            name="coordinate_system",
            datatype="GPSpatialReference",
            parameterType="Optional",
            direction="Input")

        # Number of points field.
        numPointsFieldName = arcpy.Parameter(
            displayName="Number of Points Field",
            name="num_points_field",
            datatype="GPString",
            parameterType="Optional",
            direction="Input")

        return [
            srcPoints, destPoints, networkDataset, numBands, begDist, distInc,
            snapDist, outNetKLoc, outRawODCMFCName, outRawFCName, outAnlFCName,
            numPerms, outCoordSys, numPointsFieldName
        ]

    ###
    # Check if the tool is available for use.
    ###
    def isLicensed(self):
        # Network Analyst tools must be available.
        return arcpy.CheckExtension("Network") == "Available"

    ###
    # Set parameter defaults.
    ###
    def updateParameters(self, parameters):
        networkDataset = parameters[2].value
        outCoordSys = parameters[12].value

        # Default the coordinate system.
        if networkDataset is not None and outCoordSys is None:
            ndDesc = arcpy.Describe(networkDataset)
            # If the network dataset's coordinate system is a projected one,
            # use its coordinate system as the defualt.
            if (ndDesc.spatialReference.projectionName != ""
                    and ndDesc.spatialReference.linearUnitName == "Meter"
                    and ndDesc.spatialReference.factoryCode != 0):
                parameters[12].value = ndDesc.spatialReference.factoryCode

        # Set the source of the fields (the network dataset).
        if networkDataset is not None:
            parameters[13].filter.list = self.kfHelper.getEdgeSourceFieldNames(
                networkDataset)

    ###
    # If any fields are invalid, show an appropriate error message.
    ###
    def updateMessages(self, parameters):
        outCoordSys = parameters[12].value

        if outCoordSys is not None:
            if outCoordSys.projectionName == "":
                parameters[12].setErrorMessage(
                    "Output coordinate system must be a projected coordinate system."
                )
            elif outCoordSys.linearUnitName != "Meter":
                parameters[12].setErrorMessage(
                    "Output coordinate system must have a linear unit code of 'Meter.'"
                )
            else:
                parameters[12].clearMessage()

    ###
    # Execute the tool.
    ###
    def execute(self, parameters, messages):
        srcPoints = parameters[0].valueAsText
        destPoints = parameters[1].valueAsText
        networkDataset = parameters[2].valueAsText
        numBands = parameters[3].value
        begDist = parameters[4].value
        distInc = parameters[5].value
        snapDist = parameters[6].value
        outNetKLoc = parameters[7].valueAsText
        outRawODCMFCName = parameters[8].valueAsText
        outRawFCName = parameters[9].valueAsText
        outAnlFCName = parameters[10].valueAsText
        numPerms = self.kfHelper.getPermutationSelection()[
            parameters[11].valueAsText]
        outCoordSys = parameters[12].value
        numPointsFieldName = parameters[13].value
        ndDesc = arcpy.Describe(networkDataset)
        gkfSvc = GlobalKFunctionSvc()

        # Refer to the note in the NetworkDatasetLength tool.
        if outCoordSys is None:
            outCoordSys = ndDesc.spatialReference

        messages.addMessage("\nOrigin points: {0}".format(srcPoints))
        messages.addMessage("Destination points: {0}".format(destPoints))
        messages.addMessage("Network dataset: {0}".format(networkDataset))
        messages.addMessage("Number of distance bands: {0}".format(numBands))
        messages.addMessage("Beginning distance: {0}".format(begDist))
        messages.addMessage("Distance increment: {0}".format(distInc))
        messages.addMessage("Snap distance: {0}".format(snapDist))
        messages.addMessage(
            "Path to output cross-K feature class: {0}".format(outNetKLoc))
        messages.addMessage(
            "Raw ODCM data table: {0}".format(outRawODCMFCName))
        messages.addMessage(
            "Raw cross-K data table (raw analysis data): {0}".format(
                outRawFCName))
        messages.addMessage(
            "Cross-K summary data (plottable data): {0}".format(outAnlFCName))
        messages.addMessage(
            "Number of random permutations: {0}".format(numPerms))
        messages.addMessage(
            "Network dataset length projected coordinate system: {0}".format(
                outCoordSys.name))
        messages.addMessage(
            "Number of Points Field Name: {0}\n".format(numPointsFieldName))

        # Calculate the length of the network.
        networkLength = self.kfHelper.calculateLength(networkDataset,
                                                      outCoordSys)
        messages.addMessage("Total network length: {0}".format(networkLength))

        # Count the number of crashes.
        numDests = self.kfHelper.countNumberOfFeatures(
            os.path.join(outNetKLoc, destPoints))

        # Set up a cutoff lenght for the ODCM data if possible.  (Optimization.)
        cutoff = gkfSvc.getCutoff(numBands, distInc, begDist)

        # The results of all the calculations end up here.
        netKCalculations = []

        # Use a mutable container for the number of bands so that the below callback
        # can write to it.  The "nonlocal" keyword not available in Python 2.x.
        numBandsCont = [numBands]

        # Callback function that does the Network K calculation on an OD cost matrix.
        def doNetKCalc(odDists, iteration):
            # Do the actual network k-function calculation.
            netKCalc = CrossKCalculation(networkLength, numDests, odDists,
                                         begDist, distInc, numBandsCont[0])
            netKCalculations.append(netKCalc.getDistanceBands())

            # If the user did not specifiy a number of distance bands explicitly,
            # store the number of bands.  It's computed from the observed data.
            if numBandsCont[0] is None:
                numBandsCont[0] = netKCalc.getNumberOfDistanceBands()

        # Generate the ODCM permutations, including the ODCM for the observed data.
        # doNetKCalc is called on each iteration.
        randODCMPermSvc = RandomODCMPermutationsSvc()
        randODCMPermSvc.generateODCMPermutations(
            "Cross Analysis", srcPoints, destPoints, networkDataset, snapDist,
            cutoff, outNetKLoc, outRawODCMFCName, numPerms, outCoordSys,
            numPointsFieldName, messages, doNetKCalc)

        # Store the raw analysis data.
        messages.addMessage("Writing raw analysis data.")
        gkfSvc.writeRawAnalysisData(outNetKLoc, outRawFCName, netKCalculations)

        # Analyze the data and store the results.
        messages.addMessage("Analyzing data.")
        gkfSvc.writeAnalysisSummaryData(numPerms, netKCalculations, outNetKLoc,
                                        outAnlFCName)
 def __init__(self):
   self.kfHelper = KFunctionHelper()
Example #12
0
class RandomODCMPermutationsSvc:
  ###
  # Initialize the service (stateless).
  ###
  def __init__(self):
    self.kfHelper = KFunctionHelper()

  ###
  # Generate the ODCM permutations.
  # @param analysisType Either Global Analysis or Cross Analysis.
  # @param srcPoints The source points.
  # @param destPoints The destination points.  Ignored if analysisType is GLOBAL.
  # @param networkDataset The network dataset to use for the ODCMs.
  # @param snapDist The snap distance for points that are not directly on the network.
  # @param cutoff The cutoff distance.  Ignored if None.
  # @param outLoc The full path to a database.  The ODCM data will be written here.
  # @param outFC The name of the feature class in outLoc where the ODCM data will be written.
  # @param numPerms The number of permutations (string representation).
  # @param outCoordSys The coordinate system to project the points into (optional).
  # @param numPointsFieldName The optional name of a field in the network dataset's edge source
  #        from which the number of random points should be derived.
  # @param messages A messages instances with addMessage() implemented (for debug output).
  # @param callback A callback function(odDists, iteration) called on each iteration
  #        with the current OD cost matrix.
  ###
  def generateODCMPermutations(self, analysisType, srcPoints, destPoints,
    networkDataset, snapDist, cutoff, outLoc, outFC, numPerms, outCoordSys,
    numPointsFieldName, messages, callback = None):
    # Default no-op for the callback.
    if callback is None:
      callback = lambda odDists, iteration: None

    # For global analysis the destination points are the same as the source points
    # (e.g. destPoints is ignored).
    if analysisType == "GLOBAL" or destPoints is None:
      destPoints = srcPoints

    # Count the number of crashes (there may be fewer points in the ODCM, but it's the total
    # number of crashes that is needed).
    numDests = self.kfHelper.countNumberOfFeatures(os.path.join(outLoc, destPoints))
    messages.addMessage("Number of crashes: {0}".format(numDests))

    # Make the observed ODCM and calculate the distance between each set of
    # points.  If a cross analysis is selected, find the distance between the
    # source and destination points.  Otherwise there is only one set of points
    odDists = self._calculateDistances(networkDataset, srcPoints, destPoints, snapDist, cutoff)
    self._writeODCMData(odDists, outLoc, outFC, 0)
    callback(odDists, 0)
    messages.addMessage("Iteration 0 (observed) complete.")

    # Generate the OD Cost matrix permutations.
    kfTimer = KFunctionTimer(numPerms)
    for i in range(1, numPerms + 1):
      if numPointsFieldName:
        randPoints = self.kfHelper.generateRandomPoints(networkDataset, outCoordSys, None, numPointsFieldName)
      else:
        randPoints = self.kfHelper.generateRandomPoints(networkDataset, outCoordSys, numDests, None)

      # See the note above: Either find the distance from the source points to the random points,
      # or the distance between the random points.
      if analysisType == "CROSS":
        odDists = self._calculateDistances(networkDataset, srcPoints, randPoints, snapDist, cutoff)
      else:
        odDists = self._calculateDistances(networkDataset, randPoints, randPoints, snapDist, cutoff)
      self._writeODCMData(odDists, outLoc, outFC, i)
      callback(odDists, i)

      # Clean up the random points table.
      arcpy.Delete_management(randPoints)

      # Show the progress.
      kfTimer.increment()
      messages.addMessage("Iteration {0} complete.  Elapsed time: {1}s.  ETA: {2}s.".format(
        i, kfTimer.getElapsedTime(), kfTimer.getETA()))

  ###
  # Calculate the distances between each set of points using an OD Cost Matrix.
  # @param networkDataset A network dataset which the points are on.
  # @param srcPoints The source points to calculate distances from.
  # @param destPoints The destination points to calculate distances to.
  # @param snapDist If a point is not directly on the network, it will be
  #        snapped to the nearset line if it is within this threshold.
  # @param cutoff The cutoff distance for the ODCM (optional).
  ###
  def _calculateDistances(self, networkDataset, srcPoints, destPoints, snapDist, cutoff):
    # This is the current map, which should be an OSM base map.
    curMapDoc = arcpy.mapping.MapDocument("CURRENT")

    # Get the data from from the map (see the DataFrame object of arcpy).
    dataFrame = arcpy.mapping.ListDataFrames(curMapDoc, "Layers")[0]

    # Create the cost matrix.
    costMatResult = arcpy.na.MakeODCostMatrixLayer(networkDataset, "TEMP_ODCM_NETWORK_K", "Length", cutoff)
    odcmLayer     = costMatResult.getOutput(0)

    # The OD Cost Matrix layer will have Origins and Destinations layers.  Get
    # a reference to each of these.
    odcmSublayers   = arcpy.na.GetNAClassNames(odcmLayer)
    odcmOriginLayer = odcmSublayers["Origins"]
    odcmDestLayer   = odcmSublayers["Destinations"]

    # Add the origins and destinations to the ODCM.
    arcpy.na.AddLocations(odcmLayer, odcmOriginLayer, srcPoints,  "", snapDist)
    arcpy.na.AddLocations(odcmLayer, odcmDestLayer,   destPoints, "", snapDist)

    # Solve the matrix.
    arcpy.na.Solve(odcmLayer)

    # Show the ODCM layer (it must be showing to open th ODLines sub layer below).
    #arcpy.mapping.AddLayer(dataFrame, odcmLayer, "TOP")
    #arcpy.RefreshTOC()

    # Get the "Lines" layer, which has the distance between each point.
    odcmLines = arcpy.mapping.ListLayers(odcmLayer, odcmSublayers["ODLines"])[0]

    # This array will hold all the OD distances.
    odDists = []

    if srcPoints == destPoints:
      # If the source points and destination points are the same, exclude the
      # distance from the point to itself.
      where = """{0} <> {1}""".format(
        arcpy.AddFieldDelimiters(odcmLines, "originID"),
        arcpy.AddFieldDelimiters(odcmLines, "destinationID"))
    else:
      where = ""

    with arcpy.da.SearchCursor(
      in_table=odcmLines,
      field_names=["Total_Length", "originID", "destinationID"],
      where_clause=where) as cursor:

      for row in cursor:
        odDists.append({"Total_Length": row[0], "OriginID": row[1], "DestinationID": row[2]})

    return odDists
  
  ###
  # Write the ODCM data to a table.
  # @param odDists An array of raw ODCM data.
  # @param outLoc The location of a database.
  # @param outFC The feature class name, in outLoc, to write the data to.
  # @param iteration The iteration number (0 is observed).
  ###
  def _writeODCMData(self, odDists, outLoc, outFC, iteration):
    # This is the full path to the output feature class.
    outFCFullPath = os.path.join(outLoc, outFC)

    if iteration == 0:
      # Create the output table.
      arcpy.CreateTable_management(outLoc, outFC)
      arcpy.AddField_management(outFCFullPath, "Iteration_Number", "LONG")
      arcpy.AddField_management(outFCFullPath, "OriginID",         "LONG")
      arcpy.AddField_management(outFCFullPath, "DestinationID",    "LONG")
      arcpy.AddField_management(outFCFullPath, "Total_Length",     "DOUBLE")

    with arcpy.da.InsertCursor(outFCFullPath,
      ["Iteration_Number", "OriginID", "DestinationID", "Total_Length"]) as cursor:
      for odDist in odDists:
        cursor.insertRow([iteration, odDist["OriginID"], odDist["DestinationID"], odDist["Total_Length"]])
class NetworkDatasetRandomPoints(object):
  ###
  # Initialize the tool.
  ###
  def __init__(self):
    self.label = "Create Random Points on a Network Dataset"
    self.description = "Create a series of random points on a network dataset."
    self.canRunInBackground = False

    arcpy.env.overwriteOutput = True
    self.kfHelper = KFunctionHelper()

  ###
  # Get input from the users.
  ###
  def getParameterInfo(self):
    # Network dataset.
    networkDataset = arcpy.Parameter(
      displayName="Existing Network Dataset",
      name = "network_dataset",
      datatype="Network Dataset Layer",
      parameterType="Required",
      direction="Input")

    # Output location.
    outLocation = arcpy.Parameter(
      displayName="Path to Output Random Point Feature Class",
      name="out_location",
      datatype="DEWorkspace",
      parameterType="Required",
      direction="Input")
    outLocation.value = arcpy.env.workspace

    # The random point feature class to create.
    outPointClass = arcpy.Parameter(
      displayName="Output Random Point Feature Class Name",
      name = "output_point_feature_class",
      datatype="GPString",
      parameterType="Required",
      direction="Output")

    # Num points or long.
    useField = arcpy.Parameter(
      displayName="Use a Data Field for Point Generation",
      name = "use_field",
      datatype="GPBoolean",
      parameterType="Optional",
      direction="Input")

    # Number of random points to generate.
    numPoints = arcpy.Parameter(
      displayName="Number of Points",
      name = "num_points",
      datatype="GPLong",
      parameterType="Optional",
      direction="Input")

    # Number of points field.
    numPointsFieldName = arcpy.Parameter(
      displayName="Number of Points Field",
      name = "num_points_field",
      datatype="GPString",
      parameterType="Optional",
      direction="Input")

    return [networkDataset, outLocation, outPointClass, useField, numPoints, numPointsFieldName]

  ###
  # Check if the tool is available for use.
  ###
  def isLicensed(self):
    return True

  ###
  # Set the parameter defaults.
  ###
  def updateParameters(self, parameters):
    networkDataset = parameters[0].value
    outPointClass  = parameters[2].value
    useField       = parameters[3].value
    
    if networkDataset is not None and outPointClass is None:
      # Default name for the output table.
      ndDesc = arcpy.Describe(networkDataset)
      parameters[2].value = ndDesc.name + "_Random_Points"

    # Num points/num points field is based on the useField checkbox.
    parameters[4].enabled = not useField
    parameters[5].enabled = useField

    # Set the source of the fields (the network dataset).
    if useField == True and networkDataset is not None:
      parameters[5].filter.list = self.kfHelper.getEdgeSourceFieldNames(networkDataset)

  ###
  # If any fields are invalid, show an appropriate error message.
  ###
  def updateMessages(self, parameters):
    networkDataset     = parameters[0].value
    useField           = parameters[3].value
    numPoints          = parameters[4].value
    numPointsFieldName = parameters[5].value

    if numPoints is not None:
      if numPoints <= 0:
        parameters[2].setErrorMessage("The number of points must be greater than 0.")
      else:
        parameters[2].clearMessage()

    if useField:
      if numPointsFieldName is None:
        parameters[5].setErrorMessage("Number of points field is required.")
      else:
        parameters[5].clearMessage()

      # Check that there is a single edge source if a field is used.
      if networkDataset is not None:
        if self.kfHelper.getNumEdgeSources(networkDataset) != 1:
          parameters[5].setErrorMessage("If using a field, only a single edge source is supported.")
        else:
          parameters[5].clearMessage()
    else:
      if numPoints is None:
        parameters[4].setErrorMessage("Number of points is required.")
      else:
        parameters[4].clearMessage()

  ###
  # Execute the tool.
  ###
  def execute(self, parameters, messages):
    networkDataset     = parameters[0].value
    outPath            = parameters[1].value
    outPointClass      = parameters[2].value
    useField           = parameters[3].value
    numPoints          = parameters[4].value
    numPointsFieldName = parameters[5].value

    wsPath            = arcpy.env.workspace
    ndDesc            = arcpy.Describe(networkDataset)

    messages.addMessage("Network Dataset: {0}".format(ndDesc.catalogPath))
    messages.addMessage("Location to Output Random Point Feature Class: {0}".format(outPath))
    messages.addMessage("Output Random Point Feature Class: {0}".format(outPointClass))
    messages.addMessage("Use a Data Field for Point Generation: {0}".format(useField))
    messages.addMessage("Number of Points: {0}".format(numPoints))
    messages.addMessage("Number of Points Field: {0}".format(numPointsFieldName))

    if useField:
      # Use the single edge source for the constraining feature.
      esFullPath = self.kfHelper.getEdgeSourcePath(networkDataset)

      # Create a series of random points on the new line class.
      messages.addMessage("Creating point feature class.  Name: {0} Path: {1}"
        .format(outPointClass, outPath))

      # Use the field name for the number of points (e.g. AADT).
      arcpy.CreateRandomPoints_management(out_path=outPath, out_name=outPointClass,
        constraining_feature_class=esFullPath, number_of_points_or_field=numPointsFieldName)
    else:
      # All the edge sources that make up the network dataset are combined into
      # a single feature class.
      lineClassName     = "TEMP_LINES_{0}".format(ndDesc.name)
      lineClassFullPath = os.path.join(wsPath, lineClassName)
      arcpy.CreateFeatureclass_management(out_path=wsPath, out_name=lineClassName,
        geometry_type="POLYLINE", spatial_reference=ndDesc.spatialReference)
      
      with arcpy.da.InsertCursor(lineClassName, ["SHAPE@"]) as insCursor:
        # Get the edge sources that make up the network.
        edgeSources = ndDesc.edgeSources

        for edgeSource in edgeSources:
          edgePath = os.path.join(ndDesc.path, edgeSource.name)

          with arcpy.da.SearchCursor(edgePath, ["SHAPE@"]) as cursor:
            for row in cursor:
              insCursor.insertRow([row[0]])

      # Combine all the line segments into a single line.
      singleLineName     = "TEMP_SINGLE_LINE_{0}".format(ndDesc.name)
      singleLineFullPath = os.path.join(wsPath, singleLineName)
      arcpy.Dissolve_management(lineClassFullPath, singleLineFullPath)

      # Create a series of random points on the new line class.
      messages.addMessage("Creating point feature class.  Name: {0} Path: {1}"
        .format(outPointClass, outPath))
      arcpy.CreateRandomPoints_management(out_path=outPath, out_name=outPointClass,
        constraining_feature_class=singleLineFullPath, number_of_points_or_field=numPoints)

      # Clean up the temporary feature class.
      arcpy.Delete_management(lineClassFullPath)
      arcpy.Delete_management(singleLineFullPath)
class NetworkDatasetRandomPoints(object):
    ###
    # Initialize the tool.
    ###
    def __init__(self):
        self.label = "Create Random Points on a Network Dataset"
        self.description = "Create a series of random points on a network dataset."
        self.canRunInBackground = False

        arcpy.env.overwriteOutput = True
        self.kfHelper = KFunctionHelper()

    ###
    # Get input from the users.
    ###
    def getParameterInfo(self):
        # Network dataset.
        networkDataset = arcpy.Parameter(
            displayName="Existing Network Dataset",
            name="network_dataset",
            datatype="Network Dataset Layer",
            parameterType="Required",
            direction="Input")

        # Output location.
        outLocation = arcpy.Parameter(
            displayName="Path to Output Random Point Feature Class",
            name="out_location",
            datatype="DEWorkspace",
            parameterType="Required",
            direction="Input")
        outLocation.value = arcpy.env.workspace

        # The random point feature class to create.
        outPointClass = arcpy.Parameter(
            displayName="Output Random Point Feature Class Name",
            name="output_point_feature_class",
            datatype="GPString",
            parameterType="Required",
            direction="Output")

        # Num points or long.
        useField = arcpy.Parameter(
            displayName="Use a Data Field for Point Generation",
            name="use_field",
            datatype="GPBoolean",
            parameterType="Optional",
            direction="Input")

        # Number of random points to generate.
        numPoints = arcpy.Parameter(displayName="Number of Points",
                                    name="num_points",
                                    datatype="GPLong",
                                    parameterType="Optional",
                                    direction="Input")

        # Number of points field.
        numPointsFieldName = arcpy.Parameter(
            displayName="Number of Points Field",
            name="num_points_field",
            datatype="GPString",
            parameterType="Optional",
            direction="Input")

        return [
            networkDataset, outLocation, outPointClass, useField, numPoints,
            numPointsFieldName
        ]

    ###
    # Check if the tool is available for use.
    ###
    def isLicensed(self):
        return True

    ###
    # Set the parameter defaults.
    ###
    def updateParameters(self, parameters):
        networkDataset = parameters[0].value
        outPointClass = parameters[2].value
        useField = parameters[3].value

        if networkDataset is not None and outPointClass is None:
            # Default name for the output table.
            ndDesc = arcpy.Describe(networkDataset)
            parameters[2].value = ndDesc.name + "_Random_Points"

        # Num points/num points field is based on the useField checkbox.
        parameters[4].enabled = not useField
        parameters[5].enabled = useField

        # Set the source of the fields (the network dataset).
        if useField == True and networkDataset is not None:
            parameters[5].filter.list = self.kfHelper.getEdgeSourceFieldNames(
                networkDataset)

    ###
    # If any fields are invalid, show an appropriate error message.
    ###
    def updateMessages(self, parameters):
        networkDataset = parameters[0].value
        useField = parameters[3].value
        numPoints = parameters[4].value
        numPointsFieldName = parameters[5].value

        if numPoints is not None:
            if numPoints <= 0:
                parameters[2].setErrorMessage(
                    "The number of points must be greater than 0.")
            else:
                parameters[2].clearMessage()

        if useField:
            if numPointsFieldName is None:
                parameters[5].setErrorMessage(
                    "Number of points field is required.")
            else:
                parameters[5].clearMessage()

            # Check that there is a single edge source if a field is used.
            if networkDataset is not None:
                if self.kfHelper.getNumEdgeSources(networkDataset) != 1:
                    parameters[5].setErrorMessage(
                        "If using a field, only a single edge source is supported."
                    )
                else:
                    parameters[5].clearMessage()
        else:
            if numPoints is None:
                parameters[4].setErrorMessage("Number of points is required.")
            else:
                parameters[4].clearMessage()

    ###
    # Execute the tool.
    ###
    def execute(self, parameters, messages):
        networkDataset = parameters[0].value
        outPath = parameters[1].value
        outPointClass = parameters[2].value
        useField = parameters[3].value
        numPoints = parameters[4].value
        numPointsFieldName = parameters[5].value

        wsPath = arcpy.env.workspace
        ndDesc = arcpy.Describe(networkDataset)

        messages.addMessage("Network Dataset: {0}".format(ndDesc.catalogPath))
        messages.addMessage(
            "Location to Output Random Point Feature Class: {0}".format(
                outPath))
        messages.addMessage(
            "Output Random Point Feature Class: {0}".format(outPointClass))
        messages.addMessage(
            "Use a Data Field for Point Generation: {0}".format(useField))
        messages.addMessage("Number of Points: {0}".format(numPoints))
        messages.addMessage(
            "Number of Points Field: {0}".format(numPointsFieldName))

        if useField:
            # Use the single edge source for the constraining feature.
            esFullPath = self.kfHelper.getEdgeSourcePath(networkDataset)

            # Create a series of random points on the new line class.
            messages.addMessage(
                "Creating point feature class.  Name: {0} Path: {1}".format(
                    outPointClass, outPath))

            # Use the field name for the number of points (e.g. AADT).
            arcpy.CreateRandomPoints_management(
                out_path=outPath,
                out_name=outPointClass,
                constraining_feature_class=esFullPath,
                number_of_points_or_field=numPointsFieldName)
        else:
            # All the edge sources that make up the network dataset are combined into
            # a single feature class.
            lineClassName = "TEMP_LINES_{0}".format(ndDesc.name)
            lineClassFullPath = os.path.join(wsPath, lineClassName)
            arcpy.CreateFeatureclass_management(
                out_path=wsPath,
                out_name=lineClassName,
                geometry_type="POLYLINE",
                spatial_reference=ndDesc.spatialReference)

            with arcpy.da.InsertCursor(lineClassName, ["SHAPE@"]) as insCursor:
                # Get the edge sources that make up the network.
                edgeSources = ndDesc.edgeSources

                for edgeSource in edgeSources:
                    edgePath = os.path.join(ndDesc.path, edgeSource.name)

                    with arcpy.da.SearchCursor(edgePath, ["SHAPE@"]) as cursor:
                        for row in cursor:
                            insCursor.insertRow([row[0]])

            # Combine all the line segments into a single line.
            singleLineName = "TEMP_SINGLE_LINE_{0}".format(ndDesc.name)
            singleLineFullPath = os.path.join(wsPath, singleLineName)
            arcpy.Dissolve_management(lineClassFullPath, singleLineFullPath)

            # Create a series of random points on the new line class.
            messages.addMessage(
                "Creating point feature class.  Name: {0} Path: {1}".format(
                    outPointClass, outPath))
            arcpy.CreateRandomPoints_management(
                out_path=outPath,
                out_name=outPointClass,
                constraining_feature_class=singleLineFullPath,
                number_of_points_or_field=numPoints)

            # Clean up the temporary feature class.
            arcpy.Delete_management(lineClassFullPath)
            arcpy.Delete_management(singleLineFullPath)