near analysis tool with more parameters

4583
2
Jump to solution
10-30-2014 05:35 PM
TimOpelt
New Contributor

I'm trying to find a geoprocessing tool like a beefed-up near analysis that will search outwards from a point (or polygon) of origin until a specified value (not distance or count) is reached.  Let's say I'm using a counties feature class and specify a parameter of max 1,000,000 in the population field.  The tool would begin with a user-specified county and travel outward in a radius tallying up each county's population until the parameter is met.  Any ideas, either for existing tools or model-builder?

0 Kudos
1 Solution

Accepted Solutions
XanderBakker
Esri Esteemed Contributor

Just because it is fun...

near.png

...using this code:

import arcpy
import math

def main():
    from collections import OrderedDict
    # create ordered list of distance vs id + pop
    fc = r"C:\Forum\NearEnhanced\gdb\pop_counties.gdb\counties"
    fld_pop = "PST025181D"
    fld_fips = "FIPS"

    fips_start = "29189" # "05021"
    max_pop = 5000000 # 1000000

    # get start pnt and pop
    pnt_start, pop_start = getPointStart(fc, fld_fips, fld_pop, fips_start)

    # create ordered dict with distance, fips and pop
    dct_data = createDictDistancesAndData(fc, fld_fips, fld_pop, pnt_start, fips_start)

    # let's sort on distance to true centroid
    dct_sorted = OrderedDict(sorted(dct_data.items(), key=lambda x: x[1][0]))
    lst_fips = [fips_start]
    pop_tot = pop_start
    for oid, lst in dct_sorted.items():
        pop = lst[1]
        pop_tot += pop
        if pop_tot > max_pop:
            print pop_tot
            break
        else:
            lst_fips.append(lst[2])

    print "{0} in ('{1}')".format(arcpy.AddFieldDelimiters(fc, fld_fips), "','".join(lst_fips))


def getPointStart(fc, fld_fips, fld_pop, fips_start):
    where = "{0} = '{1}'".format(arcpy.AddFieldDelimiters(fc, fld_fips), fips_start)
    row = arcpy.da.SearchCursor(fc, ("SHAPE@TRUECENTROID",fld_pop), where_clause=where).next()
    return row[0], row[1]

def createDictDistancesAndData(fc, fld_fips, fld_pop, pnt_start, fips_start):
    sr = arcpy.Describe(fc).spatialReference
    dct_data = {}
    where = "{0} <> '{1}'".format(arcpy.AddFieldDelimiters(fc, fld_fips), fips_start)
    with arcpy.da.SearchCursor(fc, ("SHAPE@TRUECENTROID",fld_pop, fld_fips, "OID@"), where_clause=where) as curs:
        for row in curs:
            pnt = row[0]
            pop = row[1]
            fips = row[2]
            oid = row[3]
            dist = getDistanceArcpy(pnt_start, pnt, sr)
            dct_data[oid] = [dist, pop, fips]
    return dct_data

def getDistance(pnt_start, pnt):
    return math.hypot(pnt[0] - pnt_start[0], pnt[1] - pnt_start[1])

def getDistanceArcpy(pnt1, pnt2, sr):
    pnt_geo1 = arcpy.PointGeometry(arcpy.Point(pnt1[0], pnt1[1]), sr)
    pnt_geo2 = arcpy.PointGeometry(arcpy.Point(pnt2[0], pnt2[1]), sr)
    return pnt_geo1.distanceTo(pnt_geo2)

if __name__ == '__main__':
    main()

The example is using county with fips 29189 and a max population of 5 million. Change lines 7 to 9 to indicate the featureclass with the counties and the names of the fips and populations fields. Change the settings on line 11 and 12 to indicate the fips to start and the maximum populations.

The result is a print of the where clause you can apply to show the counties included in the selection. The code can be adapted to create a feature class with the counties.

The code stops with the value before it passes the maximum population. This happens on line 27 to 29.

View solution in original post

0 Kudos
2 Replies
XanderBakker
Esri Esteemed Contributor

Just because it is fun...

near.png

...using this code:

import arcpy
import math

def main():
    from collections import OrderedDict
    # create ordered list of distance vs id + pop
    fc = r"C:\Forum\NearEnhanced\gdb\pop_counties.gdb\counties"
    fld_pop = "PST025181D"
    fld_fips = "FIPS"

    fips_start = "29189" # "05021"
    max_pop = 5000000 # 1000000

    # get start pnt and pop
    pnt_start, pop_start = getPointStart(fc, fld_fips, fld_pop, fips_start)

    # create ordered dict with distance, fips and pop
    dct_data = createDictDistancesAndData(fc, fld_fips, fld_pop, pnt_start, fips_start)

    # let's sort on distance to true centroid
    dct_sorted = OrderedDict(sorted(dct_data.items(), key=lambda x: x[1][0]))
    lst_fips = [fips_start]
    pop_tot = pop_start
    for oid, lst in dct_sorted.items():
        pop = lst[1]
        pop_tot += pop
        if pop_tot > max_pop:
            print pop_tot
            break
        else:
            lst_fips.append(lst[2])

    print "{0} in ('{1}')".format(arcpy.AddFieldDelimiters(fc, fld_fips), "','".join(lst_fips))


def getPointStart(fc, fld_fips, fld_pop, fips_start):
    where = "{0} = '{1}'".format(arcpy.AddFieldDelimiters(fc, fld_fips), fips_start)
    row = arcpy.da.SearchCursor(fc, ("SHAPE@TRUECENTROID",fld_pop), where_clause=where).next()
    return row[0], row[1]

def createDictDistancesAndData(fc, fld_fips, fld_pop, pnt_start, fips_start):
    sr = arcpy.Describe(fc).spatialReference
    dct_data = {}
    where = "{0} <> '{1}'".format(arcpy.AddFieldDelimiters(fc, fld_fips), fips_start)
    with arcpy.da.SearchCursor(fc, ("SHAPE@TRUECENTROID",fld_pop, fld_fips, "OID@"), where_clause=where) as curs:
        for row in curs:
            pnt = row[0]
            pop = row[1]
            fips = row[2]
            oid = row[3]
            dist = getDistanceArcpy(pnt_start, pnt, sr)
            dct_data[oid] = [dist, pop, fips]
    return dct_data

def getDistance(pnt_start, pnt):
    return math.hypot(pnt[0] - pnt_start[0], pnt[1] - pnt_start[1])

def getDistanceArcpy(pnt1, pnt2, sr):
    pnt_geo1 = arcpy.PointGeometry(arcpy.Point(pnt1[0], pnt1[1]), sr)
    pnt_geo2 = arcpy.PointGeometry(arcpy.Point(pnt2[0], pnt2[1]), sr)
    return pnt_geo1.distanceTo(pnt_geo2)

if __name__ == '__main__':
    main()

The example is using county with fips 29189 and a max population of 5 million. Change lines 7 to 9 to indicate the featureclass with the counties and the names of the fips and populations fields. Change the settings on line 11 and 12 to indicate the fips to start and the maximum populations.

The result is a print of the where clause you can apply to show the counties included in the selection. The code can be adapted to create a feature class with the counties.

The code stops with the value before it passes the maximum population. This happens on line 27 to 29.

0 Kudos
XanderBakker
Esri Esteemed Contributor

Small addition on what it actually does:

  • line 15: it gets the start point (true centroid of the county, based on the FIPS) and the corresponding population of that county.
  • line 18: it creates a dictionary containing the oid as key and a list of distance, population and fips of the counties as values
  • line 21: the dictionary is ordered based on the distance from each centroid to the start county
  • line 24: a loop is started to accumulate the population until the desired population is reached

The result is printed as where clause that can be copied into the layer properties to see the result.

0 Kudos