Select to view content in your preferred language

add time constraint to near tool

6622
18
Jump to solution
04-08-2015 08:30 AM
QiuhuaMa
Deactivated User

Hi,

I am working on my dissertation right now and need to use Arcgis to calculate the distance from the nearest fire to each house.

Since fire is polygon and house is point, I just use near tool to calculate distance from the nearest fire to the house. However the fires I am interested in is the fire that burned within the past 7 years before the sale of the homes. So I need to exclude all fires that burned after the sale date of the home, and all fires that burned greater than seven years before the sale of the home.

Is there anyway to add a time constraint to near tool using python? 

thanks,

chelsea

0 Kudos
1 Solution

Accepted Solutions
XanderBakker
Esri Esteemed Contributor

OK, I had some fun with the data and this is what I obtained as result (houses drawn on distance to nearest fire):

Dist2Fire.png

Preparation:

  • I projected the Fire polygons to the same spatial reference as the houses

Change in the code below:

  • paths to input data on lines 7 and 8

Code:

import arcpy
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta

def main():
    # input fc's
    fc_house = r"C:\Forum\DistFireHouse\shp\housesaledata.shp"
    fc_fire = r"C:\Forum\DistFireHouse\shp\firehistory_NAD_fips3002_feet.shp"

    # input field
    fld_date_sale = "ma_date_dt"

    # output fields
    fld_dist = "Dist2Fire"
    fld_fire_oid = "FireOID"

    # add fields
    addField(fc_house, fld_dist, "DOUBLE")
    addField(fc_house, fld_fire_oid, "LONG")

    # loop through houses
    cnt = 0
    flds = ("SHAPE@", fld_date_sale, fld_dist, fld_fire_oid)
    with arcpy.da.UpdateCursor(fc_house, flds) as curs:
        for row in curs:
            cnt += 1
            if cnt % 25 == 0:
                print "Processing house: {0}".format(cnt)
            pnt = row[0]
            date_sale = row[1]
            row[2], row[3] = getNearestFire(fc_fire, pnt, date_sale)
            curs.updateRow(row)


def addField(fc, fldname, fldtype):
    if len(arcpy.ListFields(fc, wild_card=fldname)) == 0:
        arcpy.AddField_management(fc, fldname, fldtype)

def getNearestFire(fc, pnt, date_sale):
    # input fields
    fld_year = "FIRE_YEAR"
    fld_month = "FIRE_MONTH"
    fld_day = "FIRE_DAY"

    # defaults
    min_dist = 999999
    oid_fire = -1

    flds = ("SHAPE@", "OID@", fld_year, fld_month, fld_day)
    with arcpy.da.SearchCursor(fc, flds) as curs:
        for row in curs:
            polygon = row[0]
            oid = row[1]
            year = int(row[2])
            try:
                month = int(row[3])
            except:
                month = 6
            try:
                day = int(row[4])
            except:
                day = 15

            if chkDate(date_sale, year, month, day):
                dist = pnt.distanceTo(polygon)
                if dist < min_dist:
                    min_dist = dist
                    oid_fire = oid

    return min_dist, oid_fire

def chkDate(date_sale, year_fire, month_fire, day_fire):
    date_fire = datetime(year=year_fire, month=month_fire, day=day_fire)
    date_2monthsbefore_sale = date_sale + relativedelta(months=-2)
    year_sale = date_sale.year
    test = (year_fire >= year_sale - 7) and (date_fire < date_2monthsbefore_sale)
##    if test:
##        print "date_sale: {0}".format(date_sale)
##        print "date_fire: {0}".format(date_fire)
##        print "date_2monthsbefore_sale: {0}".format(date_2monthsbefore_sale)
##        print "year_sale: {0}".format(year_sale)
##        print "{0}  <  {1}  <  {2}".format(year_sale-7, date_fire, date_2monthsbefore_sale)
    return test

if __name__ == '__main__':
    main()

View solution in original post

18 Replies
JamesCrandall
MVP Frequent Contributor

Can you just apply a definition query on the fire layer that meets your requirements then run your tool?  Or do you have a specific python script that needs modified to accomplish this?

0 Kudos
XanderBakker
Esri Esteemed Contributor

I assume that the sale of each house can have a different date, right? In that case the definition query on the fires per house would be different. In that case you don't really need to use the Near tool, but you could use the geometries to determine the distance. Do you have a maximum search distance defined?

This would require a loop through the houses, determine the date of sale, define the where clause based on the date to use in the search cursor for the fire polygons, calculate the distance, keep track of the shortest distance and store this as a result probably as an attribute for the current house.

If that is what you are looking for, and maybe if you can share a small part of your data, we can create a script to get that result.

0 Kudos
QiuhuaMa
Deactivated User

Xander, you are right! Each house has a different sale date. I didn't

define the maximum search distance.

What you said is exactly what I want to do! I tried to do this manually but

it took so much time!

I attached part of my house sale data and fire history data. Parcelid is

parcel id for each house. Ma_date_dt is sale date of each house.

For each fire, I have fire_day, fire_month and fire_year!

If a house is sold on 07/15/2013, I would like to find the nearest fire to

this house that burned between 2006 and 05/2013.

if a house is sold on 05/02/2011, I would like to find the nearest fire to

the house that burned between 2004 to 03/2011.

I would like to restrict the beginning time to year but the end time

to month/year.

thanks so much,

Chelsea

0 Kudos
QiuhuaMa
Deactivated User

the end time of fire need to be 2 month earlier than house sale time since

most sale process took longer than 2 month.

XanderBakker
Esri Esteemed Contributor

OK, I had some fun with the data and this is what I obtained as result (houses drawn on distance to nearest fire):

Dist2Fire.png

Preparation:

  • I projected the Fire polygons to the same spatial reference as the houses

Change in the code below:

  • paths to input data on lines 7 and 8

Code:

import arcpy
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta

def main():
    # input fc's
    fc_house = r"C:\Forum\DistFireHouse\shp\housesaledata.shp"
    fc_fire = r"C:\Forum\DistFireHouse\shp\firehistory_NAD_fips3002_feet.shp"

    # input field
    fld_date_sale = "ma_date_dt"

    # output fields
    fld_dist = "Dist2Fire"
    fld_fire_oid = "FireOID"

    # add fields
    addField(fc_house, fld_dist, "DOUBLE")
    addField(fc_house, fld_fire_oid, "LONG")

    # loop through houses
    cnt = 0
    flds = ("SHAPE@", fld_date_sale, fld_dist, fld_fire_oid)
    with arcpy.da.UpdateCursor(fc_house, flds) as curs:
        for row in curs:
            cnt += 1
            if cnt % 25 == 0:
                print "Processing house: {0}".format(cnt)
            pnt = row[0]
            date_sale = row[1]
            row[2], row[3] = getNearestFire(fc_fire, pnt, date_sale)
            curs.updateRow(row)


def addField(fc, fldname, fldtype):
    if len(arcpy.ListFields(fc, wild_card=fldname)) == 0:
        arcpy.AddField_management(fc, fldname, fldtype)

def getNearestFire(fc, pnt, date_sale):
    # input fields
    fld_year = "FIRE_YEAR"
    fld_month = "FIRE_MONTH"
    fld_day = "FIRE_DAY"

    # defaults
    min_dist = 999999
    oid_fire = -1

    flds = ("SHAPE@", "OID@", fld_year, fld_month, fld_day)
    with arcpy.da.SearchCursor(fc, flds) as curs:
        for row in curs:
            polygon = row[0]
            oid = row[1]
            year = int(row[2])
            try:
                month = int(row[3])
            except:
                month = 6
            try:
                day = int(row[4])
            except:
                day = 15

            if chkDate(date_sale, year, month, day):
                dist = pnt.distanceTo(polygon)
                if dist < min_dist:
                    min_dist = dist
                    oid_fire = oid

    return min_dist, oid_fire

def chkDate(date_sale, year_fire, month_fire, day_fire):
    date_fire = datetime(year=year_fire, month=month_fire, day=day_fire)
    date_2monthsbefore_sale = date_sale + relativedelta(months=-2)
    year_sale = date_sale.year
    test = (year_fire >= year_sale - 7) and (date_fire < date_2monthsbefore_sale)
##    if test:
##        print "date_sale: {0}".format(date_sale)
##        print "date_fire: {0}".format(date_fire)
##        print "date_2monthsbefore_sale: {0}".format(date_2monthsbefore_sale)
##        print "year_sale: {0}".format(year_sale)
##        print "{0}  <  {1}  <  {2}".format(year_sale-7, date_fire, date_2monthsbefore_sale)
    return test

if __name__ == '__main__':
    main()
QiuhuaMa
Deactivated User

thanks so much, Xander!

I am a Econ student and don't have any programming skills. I tried read

your script but can only understand a few.

I will run your code and compare it to results that I got earlier to see

whether they are consistent.

Except for fire history in the past 7 years, I also would like to do fire

history in the past 10, 15 and 20 years.

It seems to me that I only need to change the code in the following 2 lines:

def chkDate(date_sale, year_fire, month_fire, day_fire):

date_fire = datetime(year=year_fire, month=month_fire, day=day_fire)

date_2monthsbefore_sale = date_sale + relativedelta(months=-2)

year_sale = date_sale.year

test = (year_fire >= year_sale - 7) and (date_fire <

date_2monthsbefore_sale)

    1. if test:

    2. print "date_sale:

        1. print "date_fire:
      ".format(date_sale) ".format(date_fire)

    3. print "date_2monthsbefore_sale:

        1. print "year_sale:
      ".format(date_2monthsbefore_sale) ".format(year_sale)

    4. print " < < ".format(year_sale-7, date_fire,

date_2monthsbefore_sale)

return test

if __name__ == '__main__':

main()

Am I right? Are there any other changes that I need to make?

thanks,

Chelsea

0 Kudos
XanderBakker
Esri Esteemed Contributor

You are right. In fact the only change would be on line 76, where the number 7 should be replaced by the number of years you want to use. It is not necessary to change it on line 82 since it is switched of (commented), but if you uncomment the lines, you would have to adapt that number too.

BUT, also make sure that you point the result to different output fields or to a different copy of your input input houses. If you don't the new run will overwrite the data in the existing fields.

If you consider that an answers has been helpful you can mark an answer as such using the link "Helpful Yes | No" below each post.

0 Kudos
QiuhuaMa
Deactivated User

thanks for the reminder about overwriting problem.

Actually I get confused starting at # loop through houses! Is this loop

still consistent with what you posted earlier? For each house, find all

fires in the past 7 years based on house sale date. Then calculate distance

of each fire to the house and store the shorted one!

Chelsea

0 Kudos
XanderBakker
Esri Esteemed Contributor

If you are interested in understanding the code, I can give you some pointers, but this example may not be the best way of learning python. But, anyway, here goes:

  • Lines 1 to 3 do all the import outside the functions (the "def"s) so that all the functions can make use of them
  • The code actually starts at the very last line (86) calling the main def on line 5
  • As stated before lines 7 and 8 hold references to the input shapefiles
  • Line 11 has the name of the input date field in the house shapefile
  • Lines 14 and 15 have the names for the output fields to be added to input house shapefile (with the result of the analysis)
  • Lines 17 and 18 call a custom function addField (defined on line 35) which checks if the field already exists and if not (length of the list of fields with the provided name is 0) the field is added to the shapefile
  • Line 22 defines a counter variable cnt that will be incremented for each row in the house shapefile
    • Lines 26-28 provide some information for the user on the progress (every 25th record it will write a message "Processing house: #" where # is the counter
  • Line 24 starts the update cursor on the houses and it take the list of fields defined on line 23
  • Line 25 will go through each row in the cursor
  • Line 29 reads the geometry, [0] refers to the first index of the list of field, in this case "SHAPE@"
  • Line 30 read the input date of sale of the house
  • Line 31 triggers the analysis. It calls a user defined function called "getNearestFire" defined on line 39 which will return two variables: distance and oid of fire. These will be written to the fields of the row (field [2] and [3]).
  • Line 39 the does the analysis of determining which fire is closest with based on a given date
  • Line 41-43 define the input fields that hold the date of the fire
  • Line 46 and 47 set some initial values for the minimum distance found and the corresponding oid
  • Line 49-51 start the cursor on the fires
  • It extracts the polygon, the oid and the fields for the date of fire
    • note that the try, except lines (55-62) is for those cases that the month and day fields are empty
  • Next the chkDate function is called which is defined on line 72 and hold the logic for determining of the fire is in the date range that we are looking for
  • The chkDate function on line 72:
    • creates a date object for the fire
    • calculates the date two months before the house was sold
    • defined the year of the sale
    • and tests is the date is what we are looking for (returns True) or not (returns False)
  • On line 64 this result is used. If it is in the date range, the distance between the point and the polygon is calculated (this required the data to be in the same projection) on line 65
  • On line 66 it checks if the distance is less than the minimum distance, and if so it assigns it to the minimum distance and sets the oid to the oid_fire
  • On line 70 the minimum distance and oid of the corresponding fire is returned. which is read on line 31
  • On line 32, the row with the minimum distance and fire oid is stored.

That is basically it...

Kind regards, Xander