Just to add some explanation of what I did. I created two scripts, one to create the spiral and the other one to order the polygons.
The first script to create the spiral looks like this:
def main():
import arcpy
from math import pi, sin, cos
arcpy.env.overwriteOutput = True
fc_parcels = r'C:\GeoNet\Spiral\Parcels.shp'
fc = r'C:\GeoNet\Spiral\data.gdb\oval_spiral_v01'
sr = arcpy.Describe(fc_parcels).spatialReference
scale_extent = 1.25
pnts_cnt = 1000
pnts_div = 40.0
ext = arcpy.Describe(fc_parcels).extent
ext = ScaleExtent(ext, scale_extent)
lst_x, lst_y, lst_xy = [], [],[]
lst = range(pnts_cnt)
for i in reversed(lst):
phi = float(i) / pnts_div * pi
x = phi * cos(phi)
y = phi * sin(phi)
lst_x.append(x)
lst_y.append(y)
lst_xy.append((x, y))
x_factor, y_factor, x_offset, y_offset = DefineScaling(lst_x, lst_y, ext)
pnts = []
for xy in lst_xy:
x = xy[0] * x_factor + x_offset
y = xy[1] * y_factor + y_offset
pnt = arcpy.Point(x, y)
pnts.append(pnt)
polyline = arcpy.Polyline(arcpy.Array(pnts), sr)
arcpy.CopyFeatures_management([polyline], fc)
def ScaleExtent(ext, scale_extent):
new_width = ext.width * scale_extent
new_height = ext.height * scale_extent
x_cc = (ext.XMin + ext.XMax) / 2.0
y_cc = (ext.YMin + ext.YMax) / 2.0
return arcpy.Extent(x_cc - new_width / 2.0, y_cc - new_height / 2.0,
x_cc + new_width / 2.0, y_cc + new_height / 2.0)
def DefineScaling(lst_x, lst_y, ext_out):
xmin, xmax = min(lst_x), max(lst_x)
ymin, ymax = min(lst_y), max(lst_y)
width_in = xmax - xmin
height_in = ymax - ymin
x_factor = ext_out.width / width_in
y_factor = ext_out.height / height_in
x_offset = ext_out.XMin - xmin * x_factor
y_offset = ext_out.YMin - ymin * y_factor
return x_factor, y_factor, x_offset, y_offset
if __name__ == '__main__':
main()
And it creates the spiral line.
Just to validate if it crosses all the polygons I manually did a select by location (but you can easily script this too).In case it doesn't select all the parcels, one should play with the values for pnts_cnt and pnts_div which define the number of spins.
In case it does select all the second step is to define the order of the parcels. This is done by first performing an intersect between the spiral line and the parcels. This too I did manually, but you can script this also. The resulting line featureclass will have all the intersecting segments. Next you will have to do a Multipart to Singlepart to ceate a feature for each part.
The order is then defined by the second script that validate the center of the segment on its position on the spiral. The lower this position (0-100%) the lower the assign value for the order.
def main():
import arcpy
fc = r'C:\GeoNet\Spiral\data.gdb\spiral_parcel_intersect_mp2sp_v01'
fld_id = 'FID_Parcels'
fc_spiral = r'C:\GeoNet\Spiral\data.gdb\oval_spiral_v01'
fc_parcels = r'C:\GeoNet\Spiral\Parcels.shp'
fld_out = 'Result'
dct_fc ={r[0]:[r[1], r[2]] for r in arcpy.da.SearchCursor(fc, ('OID@', 'SHAPE@', fld_id))}
spiral = arcpy.da.SearchCursor(fc_spiral, ('SHAPE@')).next()[0]
dct_parcels = {}
for oid, lst in dct_fc.items():
polyline = lst[0]
fid = lst[1]
pntg = polyline.positionAlongLine(0.5, True)
pos = spiral.measureOnLine(pntg, True)
if fid in dct_parcels:
min_pos = dct_parcels[fid]
if pos < min_pos:
dct_parcels[fid] = pos
else:
dct_parcels[fid] = pos
dct_res = {}
cnt = 0
for fid, pos in sorted(dct_parcels.items(), key=lambda x: x[1]):
cnt += 1
dct_res[fid] = cnt
if len(arcpy.ListFields(fc_parcels, fld_out)) == 0:
arcpy.AddField_management(fc_parcels, fld_out, "SHORT")
flds = ('OID@', fld_out)
with arcpy.da.UpdateCursor(fc_parcels, flds) as curs:
for row in curs:
fid = row[0]
if fid in dct_res:
row[1] = dct_res[fid]
curs.updateRow(row)
if __name__ == '__main__':
main()
The result will look like this:
Since your spiral is more rectangular, the code would have to generate the "spiral" in a different way.