Calculate Distance multipoints to multiple Polylines

2198
17
Jump to solution
12-15-2017 03:03 AM
ChristophBaumeister
New Contributor II

Hello everyone,

I have a problem calculating average distances between multiple points to multiple polylines. I have two tables: one is with mapped points, the other is with mapped polylines. The ID number of both tables match. I would like to calculate the average distance of a certain set of points that match to the ID of the certain set of polylines. Example: I would like to calculate the average distance of two homes of one city dweller to three roads in the city he likes most. So, two points (homes) and three polylines (roads) have the same ID since they belong to this unique person. Is there an easy way to calculate the respective distances in ArcGIS? Since I have a set of data with many attributes like this, I cannot calculate it manually by pre-selecting attributes. The near tool does not work.


Could somebody help me to resolve the problem?

Thanks a lot in advance,

Christoph

0 Kudos
17 Replies
XanderBakker
Esri Esteemed Contributor

My recommendation would be to use geometries in case they are available. I imported the excel tables into a file geodatabase and created events in order to visualize the data you have.

I created a script to generate some results, which could guide us to define what result you're after and how to store this.See code below:

def main():
    import arcpy

    # settings
    fc_line = r'C:\GeoNet\DistLinePoints\data.gdb\lines_neu'
    fld_person_line = 'person'
    fld_orig_fid_line = 'ORIG_FID'
    fld_x_line = 'POINT_X'
    fld_y_line = 'POINT_Y'

    fc_pnt = r'C:\GeoNet\DistLinePoints\data.gdb\points_s'
    fld_person_pnt = 'person'
    fld_x_pnt = 'POINT_X'
    fld_y_pnt = 'POINT_Y'

    fc_revi = r'C:\GeoNet\DistLinePoints\data.gdb\ValidateLines02'

    sr = arcpy.SpatialReference(4326) # assuming GCS_WGS_1984

    # create list of ids in lines fc and pnts fc
    lst_ids_line = list(set([int(r[0]) for r in arcpy.da.SearchCursor(fc_line, (fld_person_line))]))
    lst_ids_pnt = list(set([int(r[0]) for r in arcpy.da.SearchCursor(fc_pnt, (fld_person_pnt))]))

    # Determine the ids that are in both lists
    lst_ids_both = list(set(lst_ids_line) & set(lst_ids_pnt))
    print "person ids to process:"
    for person in lst_ids_both:
        print " - ", person
    print

    # person ids to skip
    lst_ids_skip = list(set(lst_ids_pnt) - set(lst_ids_line))
    print "person ids to skip (no lines available):"
    for person in lst_ids_skip :
        print " - ", person
    print

    # create dictionary id vs points corresponding to lines
    dct_lines = {}
    flds = (fld_person_line, fld_orig_fid_line, fld_x_line, fld_y_line)
    with arcpy.da.SearchCursor(fc_line, flds) as curs:
        for row in curs:
            person = int(row[0])
            org_fid = int(row[1])
            x = row[2]
            y = row[3]
            pnt = arcpy.Point(x, y)
            pntg = arcpy.PointGeometry(pnt, sr)
            if person in dct_lines:
                data = dct_lines[person]
                data.append([org_fid, pntg])
                dct_lines[person] = data
            else:
                data = [[org_fid, pntg]]
                dct_lines[person] = data


    # create dictionary oid vs [person, pntg]
    flds = ('OID@', fld_person_pnt, fld_x_pnt, fld_y_pnt)
    dct_points = {r[0]: [int(r[1]), arcpy.PointGeometry(arcpy.Point(r[2], r[3]), sr)]
                        for r in arcpy.da.SearchCursor(fc_pnt, flds)}


    # process points
    lst_feat = []
    for person in lst_ids_both:
        lines = []
        lines = dct_lines[person]
        for oid, data in dct_points.items():
            if data[0] == person:
                pntg_pnt = data[1]
                min_dist = 99999
                min_dist_orig_fid = None
                lst_dist = []
                cnt = 0
                for line in lines:
                    cnt += 1
                    orig_fid = int(line[0])
                    pntg_line = line[1]
                    dist = pntg_pnt.angleAndDistanceTo(pntg_line, 'GEODESIC')[1]
                    lst_dist.append(dist)
                    # print " - ", cnt, dist, min_dist
                    if dist < min_dist:
                        pntg_near = pntg_line
                        min_dist = dist
                        min_dist_orig_fid = orig_fid
                print "oid: {0}   person: {1}".format(oid, person)
                print " - min dist: {0}   orig_fid: {1}".format(min_dist, min_dist_orig_fid)
                print " - line points: {0}  mean dist: {1}   max dist: {2}\n".format(len(lst_dist),
                            sum(lst_dist) / float(len(lst_dist)), max(lst_dist))
                polyline = arcpy.Polyline(arcpy.Array([pntg_pnt.firstPoint, pntg_near.firstPoint]), sr)
                lst_feat.append(polyline)

    # store revision lines
    arcpy.CopyFeatures_management(lst_feat, fc_revi)

if __name__ == '__main__':
    main()

This results in the following information being printed:

person ids to process:
 -  2304
 -  2305

person ids to skip (no lines available):
 -  1821

oid: 1   person: 2304
 - min dist: 1201.03805599   orig_fid: 1099
 - line points: 25  mean dist: 2472.93651947   max dist: 3463.50017465

oid: 2   person: 2304
 - min dist: 2065.10483882   orig_fid: 1099
 - line points: 25  mean dist: 3408.33879204   max dist: 4573.81500576

oid: 3   person: 2304
 - min dist: 2289.66409625   orig_fid: 1099
 - line points: 25  mean dist: 3747.19318314   max dist: 4891.74059608

oid: 4   person: 2305
 - min dist: 3191.65276967   orig_fid: 1100
 - line points: 23  mean dist: 4244.04063308   max dist: 5040.45046165

oid: 5   person: 2305
 - min dist: 6699.3446908   orig_fid: 1100
 - line points: 23  mean dist: 7721.68587451   max dist: 8449.74244931

oid: 6   person: 2305
 - min dist: 6168.04853814   orig_fid: 1100
 - line points: 23  mean dist: 7218.10916206   max dist: 8018.98016489

oid: 7   person: 2305
 - min dist: 3356.12213621   orig_fid: 1100
 - line points: 23  mean dist: 4526.67225294   max dist: 5352.57178966

Since I also create lines between the point and "line" features this help to check the result:

The larger points are the original points (houses) and the smaller points are those that represent the streets. The dashed lines connect the house to the nearest street point.

ChristophBaumeister
New Contributor II

Dear Xander,

Thanks a lot for all your effort. The result is great and works for the sample data. We will now  'translate' the script for our original data set to see, whether we can address everything we are looking for.

Thank you–that's great for the moment! 

All the best,

Christoph

0 Kudos
XanderBakker
Esri Esteemed Contributor

Your are welcome. Please post back your findings. 

0 Kudos
ChristophBaumeister
New Contributor II

Dear Xander,

we have used your script for our data analysis and it seems to work perfectly. Thank you again! However, we realized that the distance is calculated between 

C:\GeoNet\DistLinePoints\data.gdb\points_s' and C:\GeoNet\DistLinePoints\data.gdb\lines_neu'
example for distance calculation

Is there an easy way you would think of to manipulate your script in order to calculate the nearest distance to the line? I have uploaded a picture (above) to illustrate what I mean (look at the distance in the upper middle [0.000579]). 

Let me know what you think. Thanks a lot,

Christoph

0 Kudos
XanderBakker
Esri Esteemed Contributor

In fact it will be more easy, since  the script will not have to evaluate the distance to multiple points representing the line, but can use a single polyline to do this. Can you upload a small sample of the lines of the same area to adjust the code and test it?

0 Kudos
XanderBakker
Esri Esteemed Contributor

Never mind, I just converted the points to vertices using ORIG_FID to indicate the separate lines and using the order of the OBJECTID after which I added the field person with the required data.

Since there can still be multiple streets there is not much that changes. See code below:

def main():
    import arcpy

    # settings
    fc_line = r'C:\GeoNet\DistLinePoints\data.gdb\lines_neu2'
    fld_person_line = 'person'
    fld_orig_fid_line = 'ORIG_FID'

    fc_pnt = r'C:\GeoNet\DistLinePoints\data.gdb\points_s'
    fld_person_pnt = 'person'
    fld_x_pnt = 'POINT_X'
    fld_y_pnt = 'POINT_Y'

    fc_revi = r'C:\GeoNet\DistLinePoints\data.gdb\ValidateLines03'

    sr = arcpy.SpatialReference(4326) # assuming GCS_WGS_1984

    # create list of ids in lines fc and pnts fc
    lst_ids_line = list(set([int(r[0]) for r in arcpy.da.SearchCursor(fc_line, (fld_person_line))]))
    lst_ids_pnt = list(set([int(r[0]) for r in arcpy.da.SearchCursor(fc_pnt, (fld_person_pnt))]))

    # Determine the ids that are in both lists
    lst_ids_both = list(set(lst_ids_line) & set(lst_ids_pnt))
    print "person ids to process:"
    for person in lst_ids_both:
        print " - ", person
    print

    # person ids to skip
    lst_ids_skip = list(set(lst_ids_pnt) - set(lst_ids_line))
    print "person ids to skip (no lines available):"
    for person in lst_ids_skip :
        print " - ", person
    print

    # create dictionary id vs points corresponding to lines
    dct_lines = {}
    flds = (fld_person_line, fld_orig_fid_line, 'SHAPE@')
    with arcpy.da.SearchCursor(fc_line, flds) as curs:
        for row in curs:
            person = int(row[0])
            org_fid = int(row[1])
            polyline = row[2]
            if person in dct_lines:
                data = dct_lines[person]
                data.append([org_fid, polyline])
                dct_lines[person] = data
            else:
                data = [[org_fid, polyline]]
                dct_lines[person] = data


    # create dictionary oid vs [person, pntg]
    flds = ('OID@', fld_person_pnt, fld_x_pnt, fld_y_pnt)
    dct_points = {r[0]: [int(r[1]), arcpy.PointGeometry(arcpy.Point(r[2], r[3]), sr)]
                        for r in arcpy.da.SearchCursor(fc_pnt, flds)}


    # process points
    lst_feat = []
    for person in lst_ids_both:
        lines = []
        lines = dct_lines[person]
        for oid, data in dct_points.items():
            if data[0] == person:
                house = data[1]
                min_dist = 99999
                min_dist_orig_fid = None
                lst_dist = []
                cnt = 0
                for line in lines:
                    cnt += 1
                    orig_fid = int(line[0])
                    street = line[1]
                    pntg_street = street.snapToLine(house)
                    dist = house.angleAndDistanceTo(pntg_street, 'GEODESIC')[1]
                    lst_dist.append(dist)
                    # print " - ", cnt, dist, min_dist
                    if dist < min_dist:
                        pntg_near = pntg_street
                        min_dist = dist
                        min_dist_orig_fid = orig_fid
                print "oid: {0}   person: {1}".format(oid, person)
                print " - min dist: {0}   orig_fid: {1}".format(min_dist, min_dist_orig_fid)
                print " - line points: {0}  mean dist: {1}   max dist: {2}\n".format(len(lst_dist),
                            sum(lst_dist) / float(len(lst_dist)), max(lst_dist))
                polyline = arcpy.Polyline(arcpy.Array([house.firstPoint, pntg_near.firstPoint]), sr)
                lst_feat.append(polyline)

    # store revision lines
    arcpy.CopyFeatures_management(lst_feat, fc_revi)

if __name__ == '__main__':
    main()

Example result (in red the new connections)

ChristophBaumeister
New Contributor II

Dear Xander,

Thanks a lot again. That's awesome. I wish you a Merry Christmas well in advance,

Christoph

0 Kudos
XanderBakker
Esri Esteemed Contributor

Hi Christoph.Baumeister , You're welcome. Just wondering, did you try it and if so did it yield the results you are looking for? If that is the case can you mark the answer and the "correct answer". If you haven't tested it yet, you can leave it open. Anyways I will be monitoring the thread for any follow-up. Happy Holidays!

0 Kudos