Sort WE_SN Point Feature Class

300
5
Jump to solution
05-30-2014 08:44 AM
NoahHuntington
Occasional Contributor
I have a point Feature Class I am trying to sort.  It contains both a "POINT_X" and a "POINT_Y" field.  I wish to start from lower left and sort WE_SN. I have a standard license and am therefore limited to a single sort field (excluding shape) while using sort_mgmt(). I therefore am looking to calculate new sort field using  "POINT_X" and "POINT_Y". Any suggestions?

Thanks in advance.
0 Kudos
1 Solution

Accepted Solutions
RichardFairhurst
MVP Honored Contributor
I have a point Feature Class I am trying to sort.  It contains both a "POINT_X" and a "POINT_Y" field.  I wish to start from lower left and sort WE_SN. I have a standard license and am therefore limited to a single sort field (excluding shape) while using sort_mgmt(). I therefore am looking to calculate new sort field using  "POINT_X" and "POINT_Y". Any suggestions?

Thanks in advance.


You can concatenate the X and Y coodinates in a text field, but you need to ensure the numbers are a standard length and that the decimals align for both coordinates in the same character position.  I use a Python expression like this which assigns leading and trailing zeros to maintain character positions for each coordinate for extracting State Plane Coordinates from the points:

Parser: Python

Use Codeblock: checked

Pre-Logic Script Code:
def Output(FirstPoint):   FPX = round(float(FirstPoint.X), 4)   FPY = round(float(FirstPoint.Y), 4)   return "{%(FX)012.4f}{%(FY)012.4f}" % {'FX': FPX, 'FY': FPY}


Expression: Output(!Shape.FirstPoint!)

A sample set of output coordinates in sorted order would be:

{6708902.3000}{2298340.4500}
{6708902.3000}{2298640.4500}
{6708982.3000}{2298340.4500}
{6708982.3000}{2298440.4500}
{6708982.3000}{2308440.4500}
{6709002.3000}{2298340.4500}

Every east west position coordinate takes sort priority over the north south coordinates.  This will not sort diagonally from the SW corner to the NE corner.  Instead, for a given east/west position it gets all south to north ordered positions and then moves east to the next east/west position and gets all south to north points in order at that location, etc.

This field can act as a Join field for an attribute Join in place of a Spatial Join.

Reversing the coordinate order would moves slowly from South to North with every west to east position subsorted before moving to the next position proceeding northerly in an ascending sort.

To do a diagonal sort based on closest distance to the SW corner, you would have to get the minimum X and Y coordinate of all your points to act as a corner position and then do a euclidean distance calculation of length as a third concatenated value in front of the X Y coordinate values.  The length would also have to be decimal aligned for every value with leading and trailing zeros.  This would result in a sort that fans out from the SW corner.  So for the sample points this sort order would be:

{00000.0000}{6708902.3000}{2298340.4500}
{00080.0000}{6708982.3000}{2298340.4500}
{00100.0000}{6709002.3000}{2298340.4500}
{00128.0625}{6708982.3000}{2298440.4500}
{00300.0000}{6708902.3000}{2298640.4500}
{10100.3200}{6708982.3000}{2308440.4500}

A Geographic or Arithmetic angle from the SW corner to each point could be calculated and concatenated at the beginning of the string with decimal alignment.  This would cause a radar sweep sort that goes clockwise from northwest to southeast or counterclockwise southwest to northeast from the SW corner.  With compliments of the angles or artificial adjustments of the origin axis for the angle of the sweep you could vary the sweep pattern.

View solution in original post

0 Kudos
5 Replies
RichardFairhurst
MVP Honored Contributor
I have a point Feature Class I am trying to sort.  It contains both a "POINT_X" and a "POINT_Y" field.  I wish to start from lower left and sort WE_SN. I have a standard license and am therefore limited to a single sort field (excluding shape) while using sort_mgmt(). I therefore am looking to calculate new sort field using  "POINT_X" and "POINT_Y". Any suggestions?

Thanks in advance.


You can concatenate the X and Y coodinates in a text field, but you need to ensure the numbers are a standard length and that the decimals align for both coordinates in the same character position.  I use a Python expression like this which assigns leading and trailing zeros to maintain character positions for each coordinate for extracting State Plane Coordinates from the points:

Parser: Python

Use Codeblock: checked

Pre-Logic Script Code:
def Output(FirstPoint):   FPX = round(float(FirstPoint.X), 4)   FPY = round(float(FirstPoint.Y), 4)   return "{%(FX)012.4f}{%(FY)012.4f}" % {'FX': FPX, 'FY': FPY}


Expression: Output(!Shape.FirstPoint!)

A sample set of output coordinates in sorted order would be:

{6708902.3000}{2298340.4500}
{6708902.3000}{2298640.4500}
{6708982.3000}{2298340.4500}
{6708982.3000}{2298440.4500}
{6708982.3000}{2308440.4500}
{6709002.3000}{2298340.4500}

Every east west position coordinate takes sort priority over the north south coordinates.  This will not sort diagonally from the SW corner to the NE corner.  Instead, for a given east/west position it gets all south to north ordered positions and then moves east to the next east/west position and gets all south to north points in order at that location, etc.

This field can act as a Join field for an attribute Join in place of a Spatial Join.

Reversing the coordinate order would moves slowly from South to North with every west to east position subsorted before moving to the next position proceeding northerly in an ascending sort.

To do a diagonal sort based on closest distance to the SW corner, you would have to get the minimum X and Y coordinate of all your points to act as a corner position and then do a euclidean distance calculation of length as a third concatenated value in front of the X Y coordinate values.  The length would also have to be decimal aligned for every value with leading and trailing zeros.  This would result in a sort that fans out from the SW corner.  So for the sample points this sort order would be:

{00000.0000}{6708902.3000}{2298340.4500}
{00080.0000}{6708982.3000}{2298340.4500}
{00100.0000}{6709002.3000}{2298340.4500}
{00128.0625}{6708982.3000}{2298440.4500}
{00300.0000}{6708902.3000}{2298640.4500}
{10100.3200}{6708982.3000}{2308440.4500}

A Geographic or Arithmetic angle from the SW corner to each point could be calculated and concatenated at the beginning of the string with decimal alignment.  This would cause a radar sweep sort that goes clockwise from northwest to southeast or counterclockwise southwest to northeast from the SW corner.  With compliments of the angles or artificial adjustments of the origin axis for the angle of the sweep you could vary the sweep pattern.
0 Kudos
NoahHuntington
Occasional Contributor
Thanks Richard!,

Although I am quite sure you have answered my question already. I am a little unsure about implementing this in a stand alone script.
Can you help?
def Output(FirstPoint):
  FPX = round(float(FirstPoint.X), 4)
  FPY = round(float(FirstPoint.Y), 4)
  return "{%(FX)012.4f}{%(FY)012.4f}" % {'FX': FPX, 'FY': FPY}

def get_LCP(fc):
    return os.path.join(results + '\\Simplified_' + fc)

def main():

    points = results + '\\pts'
    # Process: Find all stream crossings (points)
    #arcpy.Intersect_analysis(lines,'G:\Xcel\Route Tool\Southwest\Results.gdb\pts',"ONLY_FID","#","POINT")

    in_features = "'" + lines + "'" + " #"

    arcpy.Intersect_analysis(in_features,\
    out_feature_class="G:/Xcel/Route Tool/Southwest/Scratch.gdb/lines_clip_Intersect",\
    join_attributes="ONLY_FID",cluster_tolerance="#",output_type="POINT")

    # Adds x and y field to points
    pts_geometry = arcpy.AddXY_management("G:/Xcel/Route Tool/Southwest/Scratch.gdb/lines_clip_Intersect")

    # Dissolves duplicate points
    arcpy.Dissolve_management(pts_geometry,"pts_dissolve","POINT_X;POINT_Y","#","SINGLE_PART","DISSOLVE_LINES")

    arcpy.AddField_management("pts_dissolve","Sort","TEXT")

    arcpy.CalculateField_management("pts_dissolve","Sort", Output(Shape.FirstPoint),"PYTHON_9.3")
0 Kudos
NoahHuntington
Occasional Contributor
Thanks Richard!

I got this to work using the first process you described.  I would be curoius to see a function used to calculate that euclidean field?

Here is what worked for me.

    arcpy.AddField_management("pts_dissolve","Sort","TEXT", 50)

    Expression = "Output(!Shape.FirstPoint!)"
    codeblock = """def Output(FirstPoint):
          FPX = round(float(FirstPoint.X), 4)
          FPY = round(float(FirstPoint.Y), 4)
          return "{%(FX)012.4f}{%(FY)012.4f}" % {'FX': FPX, 'FY': FPY}"""

    arcpy.CalculateField_management("pts_dissolve","Sort", Expression,"PYTHON_9.3",codeblock)
0 Kudos
RichardFairhurst
MVP Honored Contributor
Thanks Richard!

I got this to work using the first process you described.  I would be curoius to see a function used to calculate that euclidean field?

Here is what worked for me.

    arcpy.AddField_management("pts_dissolve","Sort","TEXT", 50)

    Expression = "Output(!Shape.FirstPoint!)"
    codeblock = """def Output(FirstPoint):
          FPX = round(float(FirstPoint.X), 4)
          FPY = round(float(FirstPoint.Y), 4)
          return "{%(FX)012.4f}{%(FY)012.4f}" % {'FX': FPX, 'FY': FPY}"""

    arcpy.CalculateField_management("pts_dissolve","Sort", Expression,"PYTHON_9.3",codeblock)


This should work if you substitute my example hard coded Minimum X and Y coordinates with the hard coded values that make sense for your points (or values gathered from Summary Statistics):

    arcpy.AddField_management("pts_dissolve","Sort","TEXT", 50)

    Expression = "Output(!Shape.FirstPoint!, 6708902.3000, 2298340.4500)"
    codeblock = """def Output(FirstPoint, MinX, MinY):
          XDif = round(float(FirstPoint.X), 4) - round(float(MinX), 4)
          YDif = round(float(FirstPoint.Y), 4) - round(float(MinY), 4)
          Length = hypot(XDif, YDif)
          FPX = round(float(FirstPoint.X), 4)
          FPY = round(float(FirstPoint.Y), 4)
          return "{%(LEN)012.4f}{%(FX)012.4f}{%(FY)012.4f}" % {'LEN':Length, 'FX': FPX, 'FY': FPY}"""

    arcpy.CalculateField_management("pts_dissolve","Sort", Expression,"PYTHON_9.3",codeblock)
0 Kudos
NoahHuntington
Occasional Contributor
Thanks a million Richard!  This did exactly what I'd hoped. I used the distance method to add some error handling for SE/NW lines.

    Expression1 = "Output(!Shape.FirstPoint!, {0},{1})" .format(minX, minY)
    Expression2 = "Output(!Shape.FirstPoint!, {0},{1})" .format(maxX, minY)

    codeblock1 = """def Output(FirstPoint, MinX, MinY):
          XDif = round(float(FirstPoint.X), 4) - round(float(MinX), 4)
          YDif = round(float(FirstPoint.Y), 4) - round(float(MinY), 4)
          Length = math.hypot(XDif, YDif)
          FPX = round(float(FirstPoint.X), 4)
          FPY = round(float(FirstPoint.Y), 4)
          return "{%(LEN)012.4f}{%(FX)012.4f}{%(FY)012.4f}" % {'LEN':Length, 'FX': FPX, 'FY': FPY}"""

    codeblock2 =  """def Output(FirstPoint, MaxX, MinY):
          XDif = round(float(FirstPoint.X), 4) - round(float(MaxX), 4)
          YDif = round(float(FirstPoint.Y), 4) - round(float(MinY), 4)
          Length = math.hypot(XDif, YDif)
          FPX = round(float(FirstPoint.X), 4)
          FPY = round(float(FirstPoint.Y), 4)
          return "{%(LEN)012.4f}{%(FX)012.4f}{%(FY)012.4f}" % {'LEN':Length, 'FX': FPX, 'FY': FPY}"""

    if bearing > 180 and bearing < 270:
        arcpy.CalculateField_management("pts_dissolve","Sort", Expression,"PYTHON_9.3",codeblock)
    elif bearing > 0 and bearing < 90:
        arcpy.CalculateField_management("pts_dissolve","Sort", Expression,"PYTHON_9.3",codeblock)
    elif bearing > 90 and bearing < 180:
        arcpy.CalculateField_management("pts_dissolve","Sort", Expression2,"PYTHON_9.3",codeblock2)
    else:
        arcpy.CalculateField_management("pts_dissolve","Sort", Expression2,"PYTHON_9.3",codeblock2)
0 Kudos