Skip to content
Snippets Groups Projects
FireMain.py 29.2 KiB
Newer Older
screbec's avatar
screbec committed
""" FireMain
Main module for running the fire object tracking along time

List of functions
-----------------
* Fobj_init: Initialize the fire object for a given time
* Fire_expand: Use daily new AF pixels to create new Fobj or combine with existing Fobj
* Fire_merge: For newly formed/expanded fire objects close to existing active fires, merge them
* Fire_Forward: The wrapper function to progressively track all fire events for a time period

Modules required
----------------
* Firelog
* FireObj
* FireIO
* FireClustering
* FireVector
* FireConsts
"""

# Use a logger to record console output
from FireLog import logger

# Functions
def correct_nested_ids(mergetuple):
Yang's avatar
Yang committed
    """ correct the target fids after nested merging
    this is done before the merging happens in cases when several fires merge in one time step
    and also in the last time step to correct the heritage when several fires merge in different time steps
    Parameters
    ----------
    mergetuple: a list of tuples
        a list containing source and target ids for merging

    Returns
    -------
    mergetuple: a list of tuples
        a list containing source and corrected target ids for merging
Yang's avatar
Yang committed
    """
Yang's avatar
Yang committed
    # 1)check if all keys (source fires) are unique
    src, tgt = zip(*mergetuple)
    tgt = list(tgt)
    count_keys = collections.Counter(src)
    not_unique = [key for key in count_keys if count_keys[key] > 1]
    # if not: replace with smallest tgt number
Yang's avatar
Yang committed
    if len(not_unique) > 0:
Yang's avatar
Yang committed
            tgt1 = min(
                [tgt[ind] for ind in range(len(tgt)) if src[ind] == key]
            )  # tgt with smallest fid
            indices = [ind for ind in range(len(tgt)) if src[ind] == key]
            for ind in indices:
                tgt[ind] = tgt1
Yang's avatar
Yang committed

Yang's avatar
Yang committed
    mergetuple = list(zip(src, tgt))
    mergedict = dict(mergetuple)
    src, tgt = zip(*mergetuple)
    while any(item in src for item in set(tgt)):
        for i, tgt0 in enumerate(tgt):
            if tgt0 in src:
                mergetuple[i] = (mergetuple[i][0], mergedict[tgt0])
        src, tgt = zip(*mergetuple)
    mergetuple = list(set(mergetuple))
Yang's avatar
Yang committed

def set_eafirerngs(allfires, fids):
    """ Return a list of fire connecting ranges from a list of fire ids
Yang's avatar
Yang committed
        fire connecting range is the hull plus a buffer (connectivity_fire)
    Parameters
    ----------
    allfires : Allfires object
        the input allfires object
    fids : list
        the list of fire ids

    Returns
    -------
    eafirerngs : list
        the list of fire connecting ranges corresponding to the sequence of fids
Yang's avatar
Yang committed
    """
Yang's avatar
Yang committed
    import FireFuncs, FireVector

    # extract existing active fire data (use extending ranges)
    firerngs = []
    for fid in fids:
        f = allfires.fires[fid]  # fire
        CONNECTIVITY_FIRE_KM = FireFuncs.get_CONNECTIVITY_FIRE(f)
Yang's avatar
Yang committed
        rng = FireVector.addbuffer(f.hull, CONNECTIVITY_FIRE_KM * 1000)
Yang's avatar
Yang committed
        firerngs.append(rng)
    return firerngs

Yang's avatar
Yang committed

def set_sleeperrngs(allfires, fids):
    """ Return a list of fire connecting ranges from a list of fire ids
Yang's avatar
Yang committed
        fire connecting range is the hull plus a buffer (connectivity_sleeper)
    Parameters
    ----------
    allfires : Allfires object
        the input allfires object
    fids : list
        the list of fire ids

    Returns
    -------
    eafirerngs : list
        the list of fire connecting ranges corresponding to the sequence of fids
Yang's avatar
Yang committed
    """
Yang's avatar
Yang committed
    import FireFuncs, FireVector

    # extract existing active fire data (use extending ranges)
    sleeperrngs = []
    for fid in fids:
        f = allfires.fires[fid]  # fire
        CONNECTIVITY_SLEEPER_KM = FireFuncs.get_CONNECTIVITY_SLEEPER()
Yang's avatar
Yang committed
        rng = FireVector.addbuffer(f.hull, CONNECTIVITY_SLEEPER_KM * 1000)
Yang's avatar
Yang committed
        sleeperrngs.append(rng)
    return sleeperrngs
Yang's avatar
Yang committed

def Fobj_init(tst, regnm, restart=False):
    """ Initialize the fire object for a given time. This can be from the object
screbec's avatar
screbec committed
    saved at previous time, or can be initialized using Allfires().

    Parameters
    ----------
    tst : tuple, (int,int,int,str)
        the year, month, day and 'AM'|'PM' during the intialization
    restart : bool
        if set to true, force to initiate an object

    Returns
    -------
    allfires : Allfires obj
        the fire object for the previous time step
Yang's avatar
Yang committed
    """
Yang's avatar
Yang committed
    import FireObj, FireIO, FireTime

Yang's avatar
Yang committed
    pst = FireTime.t_nb(tst, nb="previous")  # previous time step
    if FireIO.check_fobj(pst, regnm, activeonly=False) & (restart == False):
        allfires = FireIO.load_fobj(pst, regnm, activeonly=False) # load all fires (including dead)
Yang's avatar
Yang committed
        allfires.cleanup(tst)  # update time and reset lists
Yang's avatar
Yang committed
        # # if it's the first time step of a calendar year, reset all fires id
        # if (tst[1]==1 & tst[2]==1 & tst[3]=='AM'):
        #     allfires.newyear_reset()
    else:  # if no pkl file at previous time step or restart is set to True
screbec's avatar
screbec committed
        allfires = FireObj.Allfires(tst)

    return allfires

def remove_static_sources(region, source):
    """ Modify region to exclude static sources

    Parameters
    ----------
    region : the run region. 
    source : str
        Name of the file in dirextdata that containts statics sources with a Longitude and Latitude column.  

    Returns
    -------
    region : region obj
        A region that is the diference between the user-supplied region and the points identified as static flaring/gas according to the source. Creates a "swiss cheese"- like region, with negative space where there were points, with a buffer around points determined by "remove_static_sources_buffer". 
    """
    # import
    import os
    from FireIO import get_reg_shp, gpd_read_file
    from FireConsts import dirextdata, epsg, remove_static_sources_buffer
    import shapely
    import shapely.geometry
    from shapely.geometry import Point, Polygon
    import geopandas as gpd
    
    # get source data geometry
    global_flaring = gpd_read_file(os.path.join(dirextdata,'static_sources', source))
    global_flaring = global_flaring.drop_duplicates()
    global_flaring = global_flaring[0:(len(global_flaring.id_key_2017) - 1)]

    global_flaring = gpd.GeoDataFrame(global_flaring, geometry=gpd.points_from_xy(global_flaring.Longitude, global_flaring.Latitude)) # Convert to point geometries
    global_flaring["buffer_geometry"] = global_flaring.buffer(remove_static_sources_buffer)
    global_flaring = global_flaring.set_geometry(col = "buffer_geometry")
    
    # get region geometry
    reg = get_reg_shp(region[1])
    reg_df = gpd.GeoDataFrame.from_dict({"name":[region[0]], "geometry":[reg]}) # Put geometry into dataframe for join
    
    # ensure everything is in the same projection
    global_flaring = global_flaring.set_crs("EPSG:" + str(epsg)) ## Translate to the user-input coordinate system
Tempest McCabe's avatar
Tempest McCabe committed
    reg_df = reg_df.set_crs("EPSG:" + str(epsg))
    
    # Take the difference of points and region
    diff = gpd.tools.overlay(reg_df, global_flaring, how='difference')
    
    region = (diff.name[0], diff.geometry[0])
    return(region)
    

Yang's avatar
Yang committed
def Fire_expand_rtree(allfires, afp, fids_ea, log=True):
    """ Use daily new AF pixels to create new Fobj or combine with existing Fobj
screbec's avatar
screbec committed

    Parameters
    ----------
    allfires : Allfires obj
        the existing Allfires object for the time step
    afp : 5-element list
        (lat, lon, line, sample, FRP) of new active fire pixels
screbec's avatar
screbec committed
    fids_ea : list
        fire ids of existing active fires at previous time step

    Returns
    -------
    allfires : Allfires obj
        updated Allfires object for the day with new formed/expanded fire objects
Yang's avatar
Yang committed
    """
    # import time
Yang's avatar
Yang committed
    import FireObj, FireClustering, FireVector, FireFuncs
    from FireConsts import expand_only, firessr
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    # initializations
Yang's avatar
Yang committed
    idmax = (
        allfires.number_of_fires - 1
    )  # maximum id of existing fires (max(allfires.fires.keys())?)
    fids_expanded = []  # a list of fire ids that is expanded at t
    fids_new = []  # a list of fire ids that is created at t
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    # derive fire connecting ranges of existing active fires (fids_ea)
Yang's avatar
Yang committed
    eafirerngs = set_eafirerngs(allfires, fids_ea)
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    # create a spatial index based on geometry bounds of fire connecting ranges
screbec's avatar
screbec committed
    ea_idx = FireClustering.build_rtree(eafirerngs)

    # do preliminary clustering using new active fire locations (assign cid to each pixel)
Yang's avatar
Yang committed
    afp_loc = list(zip(afp.x, afp.y))
Yang's avatar
Yang committed
    CONNECTIVITY_CLUSTER = FireFuncs.get_CONNECTIVITY_CLUSTER()
Yang's avatar
Yang committed
    cid = FireClustering.do_clustering(
        afp_loc, CONNECTIVITY_CLUSTER
    )  # cluster id for each afp_loc
Yang's avatar
Yang committed
        logger.info(f"New fire clusters of {max(cid)} at this time step")
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    # loop over all new clusters (0:cid-1) and determine its fate
    FP2expand = {}  # a diction to record {fid : Firepixel objects} pairs
Yang's avatar
Yang committed
    for ic in range(max(cid) + 1):
screbec's avatar
screbec committed
        # create cluster object using all newly detected active fires within a cluster
Yang's avatar
Yang committed
        #   (-1 means no source fireid)
Yang's avatar
Yang committed
        pixels = [
            FireObj.FirePixel(
                afp.iloc[i].x,
                afp.iloc[i].y,
                afp.iloc[i].Lon,
                afp.iloc[i].Lat,
                afp.iloc[i].FRP,
                afp.iloc[i].DS,
                afp.iloc[i].DT,
                afp.iloc[i].ampm,
                afp.iloc[i].YYYYMMDD_HHMM,
                afp.iloc[i].Sat,
                -1,
            )
            for i, v in enumerate(cid)
            if v == ic
        ]  # pixels
        cluster = FireObj.Cluster(
            ic, pixels, allfires.t, sensor=firessr
        )  # form cluster
        hull = cluster.hull  # save hull to reduce computational cost
Yang's avatar
Yang committed

        # if the cluster is close enough to an existing active fire object
        #   record all pixels to be added to the existing object (no actuall changes on existing fire objects)
Yang's avatar
Yang committed
        id_cfs = FireClustering.idx_intersection(
            ea_idx, cluster.b_box
        )  # potential neighbours using spatial index
screbec's avatar
screbec committed
        clusterdone = False
        for id_cf in id_cfs:  # loop over all potential eafires
Yang's avatar
Yang committed
            if (
                clusterdone == False
            ):  # one cluster can only be appended to one existing object
                if eafirerngs[id_cf].intersects(
                    hull
                ):  # determine if cluster touch fire connecting range
screbec's avatar
screbec committed
                    # record existing target fire id in fid_expand list
Yang's avatar
Yang committed
                    fmid = fids_ea[
                        id_cf
                    ]  # this is the fire id of the existing active fire
screbec's avatar
screbec committed
                    # record pixels from target cluster (locs and time) along with the existing active fire object id
Yang's avatar
Yang committed
                    # newFPs = [FireObj.FirePixel(p.x,p.y,p.lon,p.lat,p.frp,p.DS,p.DT,p.ampm,p.datetime,p.sat,fmid) for p in pixels] # new FirePixels from the cluster
Yang's avatar
Yang committed
                    if (
                        fmid in FP2expand.keys()
                    ):  # single existing object, can have multiple new clusters to append
                        FP2expand[fmid] = FP2expand[fmid] + pixels  # newFPs
screbec's avatar
screbec committed
                    else:
Yang's avatar
Yang committed
                        FP2expand[fmid] = pixels  # newFPs
                    fids_expanded.append(
                        fmid
                    )  # record fmid to fid_expanded ? is this same as list(FP2expand.keys)?
                    clusterdone = (
                        True  # mark the cluster as done (no need to create new Fobj)
                    )
screbec's avatar
screbec committed
        # if this cluster can't be appended to any existing Fobj, create a new fire object using the new cluster
Yang's avatar
Yang committed
        if not expand_only:  # ignore creating new fires if expand_only is set to True
Yang's avatar
Yang committed
            if (
                clusterdone is False
            ):  # if the cluster is not added to existing active fires
                # create a new fire id and add it to the fid_new list
                id_newfire = idmax + 1
                fids_new.append(id_newfire)  # record id_newfire to fid_new
                # use the fire id and new fire pixels to create a new Fire object
Yang's avatar
Yang committed
                newfire = FireObj.Fire(id_newfire, allfires.t, pixels, sensor=firessr)
Yang's avatar
Yang committed
                newfire.updateftype()  # update the fire type
                # add the new fire object to the fires list in the Allfires object
                allfires.fires[id_newfire] = newfire
                # increase the maximum id
                idmax += 1
Yang's avatar
Yang committed

    # update the expanded fire object (do the actual pixel appending and attributes changes)
    # fire attributes need to be manualy changed:
    #  - end time; - pixels; - newpixels, - hull, - extpixels
screbec's avatar
screbec committed
    if len(FP2expand) > 0:
        for fmid, newFPs in FP2expand.items():
            # the target existing fire object
            f = allfires.fires[fmid]

            # update end time
Yang's avatar
Yang committed
            f.t_ed = allfires.t
screbec's avatar
screbec committed

            # update pixels
            f.pixels = f.pixels + newFPs
            f.newpixels = newFPs
Yang's avatar
Yang committed
            # if len(newFPs) > 0:
            #     f.actpixels = newFPs
screbec's avatar
screbec committed

            # update the hull using previous hull and previous exterior pixels
Yang's avatar
Yang committed
            # phull = f.hull   # previous hull
Yang's avatar
Yang committed
            pextlocs = [p.loc for p in f.extpixels]  # previous external pixels
Yang's avatar
Yang committed
            newlocs = [p.loc for p in newFPs]  # new added pixels
            # f.hull = FireVector.update_hull(phull,pextlocs+newlocs)  # use update_hull function to save time
Yang's avatar
Yang committed
            f.updatefhull(pextlocs + newlocs)
Yang's avatar
Yang committed

            # update exterior pixels
            # f.updateextpixels(f.extpixels+newFPs)
            f.updateextpixels(newFPs)
            # f.extpixels = FireVector.cal_extpixels(f.extpixels+newFPs,f.hull)

            f.updateftype()  # update the fire type
screbec's avatar
screbec committed
            # t1 = time.time()
            # logger.info(f'Update external pixels: {t1-t2}')

    # remove duplicates and sort the fid_expanded
    fids_expanded = sorted(set(fids_expanded))

    # record fid change for expanded and new
    allfires.record_fids_change(fids_expanded=fids_expanded, fids_new=fids_new)

    return allfires

Yang's avatar
Yang committed

def Fire_merge_rtree(allfires, fids_ne, fids_ea, fids_sleep):
    """ For newly formed/expanded fires close to existing active fires or sleepers, merge them
screbec's avatar
screbec committed

    Parameters
    ----------
    allfires : Allfires obj
        the existing Allfires object for the time step
    fids_ne : list
        ids of newly formed/expanded fires
    fids_ea : list
        ids of existing active fire objects (including newly formed/expanded fires)

    Returns
    -------
    allfires : Allfires obj
        Allfires obj after fire merging
Yang's avatar
Yang committed
    """
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    import FireClustering, FireVector, FireFuncs
Yang's avatar
Yang committed
    from FireConsts import firessr  # CONNECTIVITY_THRESHOLD_KM
screbec's avatar
screbec committed

    # extract existing active fire data (use extending ranges)
Yang's avatar
Yang committed
    eafirerngs = set_eafirerngs(allfires, fids_ea)
Yang's avatar
Yang committed
    # create a spatial index based on geometry bounds of fire connecting ranges
    ea_idx = FireClustering.build_rtree(eafirerngs)
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    # extract new and recently expanded fire data (use hulls without buffer)
Yang's avatar
Yang committed
    nefires = [allfires.fires[fid] for fid in fids_ne]
Yang's avatar
Yang committed
    nefirehulls = [f.hull for f in nefires]

screbec's avatar
screbec committed
    # loop over all fire objects that have newly expanded or formed, record merging fire id pairs
Yang's avatar
Yang committed
    fids_merge = []  # initialize the merged fire id pairs (source id:target id)
Yang's avatar
Yang committed
    firedone = {
        i: False for i in fids_ne
    }  # flag to mark an newly expanded fire obj that has been invalidated
screbec's avatar
screbec committed
    for id_ne in range(len(nefires)):
Yang's avatar
Yang committed
        fid_ne = fids_ne[id_ne]  # newly formed/expanded fire id
        if (
            firedone[fid_ne] == False
        ):  # skip objects that have been merged to others in earlier loop
screbec's avatar
screbec committed
            # potential neighbors
            id_cfs = FireClustering.idx_intersection(ea_idx, nefirehulls[id_ne].bounds)
screbec's avatar
screbec committed
            # loop over all potential neighbor fobj candidiates
            for id_ea in id_cfs:
                fid_ea = fids_ea[id_ea]  # fire id of existing active fire
                # if fid_ne == fid_ea, skip;
                # if the expanded fire has been merged to a existing active fire, skip the rest loops
Yang's avatar
Yang committed
                if fid_ne != fid_ea:
screbec's avatar
screbec committed
                    # if fire fmid is within distance of fire fid, two objects will merge
                    if nefirehulls[id_ne].intersects(eafirerngs[id_ea]):
                        # the fire id of neighboring active Fobj
                        # depending on which fid is smaller, merge the two fire objects in different directions
                        if fid_ea > fid_ne:  # merge fid_ea to fid_ne
Yang's avatar
Yang committed
                            fids_merge.append((fid_ea, fid_ne))
screbec's avatar
screbec committed
                            if fid_ea in firedone.keys():
Yang's avatar
Yang committed
                                firedone[
                                    fid_ea
                                ] = True  # remove fid_ea from the newly expanded fire list (since it has been invalidated)
                        else:  # merge fid_ne to fid_ea
                            fids_merge.append((fid_ne, fid_ea))
screbec's avatar
screbec committed
                            # fid_ne is merged to others, so stop it and check the next id_ne
                            ## technically the eafirerngs and nefirehulls have to be updated before the next loop happens
                            ## else it can happen that intersections are not being detected
screbec's avatar
screbec committed
                            ## if more than two fires grow together!!
Yang's avatar
Yang committed
                            # break
Yang's avatar
Yang committed
    # now check if any of the sleeper fires may have reactivated by new/expanded fires
Yang's avatar
Yang committed
    if len(fids_sleep) > 0:  # check if there are potential sleepers
        # extract existing sleeping fires and their firelines
Yang's avatar
Yang committed
        sleepfires = [allfires.fires[fid] for fid in fids_sleep]
Yang's avatar
Yang committed

        # sleepflines = [f.fline for f in sleepfires]; if no fline, use fline_prior
Yang's avatar
Yang committed
        sleepflines = [
            f.fline if f.fline is not None else f.fline_prior for f in sleepfires
        ]
Yang's avatar
Yang committed
        # extract ne fires sleeper range
Yang's avatar
Yang committed
        nefiresleeperrangs = set_sleeperrngs(allfires, fids_ne)
Yang's avatar
Yang committed

        # create a spatial index based on geometry bounds of ne fire sleeper ranges
        ne_idx = FireClustering.build_rtree(nefiresleeperrangs)
Yang's avatar
Yang committed
        # nefirebuf  = [FireVector.addbuffer(hull,sleeperthresh*1000) for hull in nefirehulls]
        # ne_idx = FireClustering.build_rtree(nefirebuf)

        # do the check analoguous to above; loop over each sleeper fire
Yang's avatar
Yang committed
        firedone = {
            i: False for i in fids_sleep
        }  # flag to mark an sleeper fire obj that has been invalidated
        for id_sleep in range(len(sleepfires)):
Yang's avatar
Yang committed
            fid_sleep = fids_sleep[id_sleep]  # sleeper fire id
            if (
                sleepflines[id_sleep] == None
            ):  # if there is no fire line (last active detection within), skip
Yang's avatar
Yang committed
            if (
                firedone[fid_sleep] == False
            ):  # skip objects that have been merged to others in earlier loop
Yang's avatar
Yang committed
                id_cfs = FireClustering.idx_intersection(
                    ne_idx, sleepflines[id_sleep].bounds
                )
                # loop over all potential neighbour fobj candidates
                for id_ne in id_cfs:
Yang Chen's avatar
Yang Chen committed
                    fid_ne = fids_ne[id_ne]
Yang's avatar
Yang committed
                    if nefiresleeperrangs[id_ne].intersects(sleepflines[id_sleep]):
                        # depending on which fid is smaller, merge the two fire objects in different directions
Yang's avatar
Yang committed
                        if (
                            fid_ne > fid_sleep
                        ):  # merge new fire to sleeper, reactivate sleeper
                            fids_merge.append((fid_ne, fid_sleep))
                            if fid_ne in firedone.keys():
Yang's avatar
Yang committed
                                firedone[
                                    fid_ne
                                ] = True  # remove fid_ne from the newly expanded fire list (since it has been invalidated)
                        else:  # merge sleeper to new or expanded fire
                            fids_merge.append((fid_sleep, fid_ne))
screbec's avatar
screbec committed
    # loop over each pair in the fids_merge, and do modifications for both target and source objects
    #  - target: t_ed; pixels, newpixels, hull, extpixels
Yang Chen's avatar
Yang Chen committed
    #  - source: invalidated
screbec's avatar
screbec committed
    if len(fids_merge) > 0:
        # fids_merge needs to be corrected if several fires merge at once!
        # i.e. if fire 2 merges into fire 1 and fire 3 merges into fire 2
        # in this case not correcting fids_merge will lead to invalidation of fire 3!!!
Yang's avatar
Yang committed
        fids_merge = correct_nested_ids(fids_merge)
Yang's avatar
Yang committed
        for fid1, fid2 in fids_merge:
screbec's avatar
screbec committed
            # update source and target objects
            f_source = allfires.fires[fid1]
            f_target = allfires.fires[fid2]

            # - target fire t_ed set to current time
            f_target.t_ed = allfires.t
Yang's avatar
Yang committed

            # just in case: set target to valid (is this needed?)
            f_target.invalid = False
screbec's avatar
screbec committed

            # - target fire add source pixels to pixels and newpixels
            f_target.pixels = f_target.pixels + f_source.pixels
            f_target.newpixels = f_target.newpixels + f_source.newpixels

            # - update the hull using previous hull and previous exterior pixels
            phull = f_target.hull
            pextlocs = [p.loc for p in f_target.extpixels]
            newlocs = [p.loc for p in f_source.pixels]
Yang's avatar
Yang committed
            # f_target.hull = FireVector.update_hull(phull,pextlocs+newlocs, sensor=firessr)
Yang's avatar
Yang committed
            f_target.updatefhull(pextlocs + newlocs)
screbec's avatar
screbec committed

            # - use the updated hull to update exterior pixels
Yang's avatar
Yang committed
            f_target.extpixels = FireVector.cal_extpixels(
                f_target.extpixels + f_source.pixels, f_target.hull
            )
screbec's avatar
screbec committed

            # invalidate and deactivate source object
screbec's avatar
screbec committed
            f_source.invalid = True
            f_source.mergeid = f_target.mergeid
Yang's avatar
Yang committed

            # update target fire ftype
            f_target.updateftype()
screbec's avatar
screbec committed

            # record the heritages
Yang's avatar
Yang committed
            # allfires.heritages.append((fid1,fid2,allfires.t))
Yang's avatar
Yang committed
            allfires.heritages.append((fid1, fid2))
screbec's avatar
screbec committed

        # remove duplicates and record fid change for merged and invalidated
Yang's avatar
Yang committed
        fids_invalid, fids_merged = zip(*fids_merge)
screbec's avatar
screbec committed
        fids_merged = sorted(set(fids_merged))
        fids_invalid = sorted(set(fids_invalid))
Yang's avatar
Yang committed
        allfires.record_fids_change(fids_merged=fids_merged, fids_invalid=fids_invalid)
screbec's avatar
screbec committed

    return allfires

Yang's avatar
Yang committed

def Fire_Forward(tst, ted, restart=False, region=None):
    """ The wrapper function to progressively track all fire events for a time period
screbec's avatar
screbec committed
           and save fire object to pkl file and gpd to geojson files
screbec's avatar
screbec committed
    Parameters
    ----------
    tst : tuple, (int,int,int,str)
        the year, month, day and 'AM'|'PM' at start time
    ted : tuple, (int,int,int,str)
        the year, month, day and 'AM'|'PM' at end time
    restart : bool
        if set to true, force to initiate an object
screbec's avatar
screbec committed
    Returns
    -------
    allfires : FireObj allfires object
        the allfires object at end date
Yang's avatar
Yang committed
    """
screbec's avatar
screbec committed
    # import libraries
Yang's avatar
Yang committed
    import FireObj, FireIO, FireTime
    from FireConsts import firesrc, firenrt, opt_rmstatfire, remove_static_sources_bool, remove_static_sources_sourcefile
Yang's avatar
Yang committed

screbec's avatar
screbec committed
    import os
    import glob
screbec's avatar
screbec committed
    # used to record time of script running
    import time
screbec's avatar
screbec committed
    t1 = time.time()
    t0 = t1
Yang's avatar
Yang committed
    # initialize allfires object
Yang's avatar
Yang committed
    allfires = Fobj_init(tst, region[0], restart=restart)
    
    # remove static sources
    if remove_static_sources_bool: 
        region = remove_static_sources(region, remove_static_sources_sourcefile)
screbec's avatar
screbec committed
    # loop over all days during the period
    endloop = False  # flag to control the ending of the loop
Yang's avatar
Yang committed
    t = list(tst)  # t is the time (year,month,day,ampm) for each step
screbec's avatar
screbec committed
    while endloop == False:
Yang's avatar
Yang committed
        logger.info("")
screbec's avatar
screbec committed
        logger.info(t)
Yang's avatar
Yang committed
        print("Fire tracking at", t)
Yang's avatar
Yang committed

        if FireTime.isyearst(t):
            allfires.newyear_reset(region[0])
Yang's avatar
Yang committed
        # 1. record existing active fire ids (before fire tracking at t)
screbec's avatar
screbec committed
        fids_ea = allfires.fids_active
Yang's avatar
Yang committed
        # 2. update t of allfires, clean up allfires and fire object
        allfires.cleanup(t)
screbec's avatar
screbec committed
        # 3. read active fire pixels from VIIRS dataset
        i = 0
        while i < 5:
            try: 
                afp = FireIO.read_AFP(t, src=firesrc, nrt=firenrt, region=region)
                break
            except Exception as e:
                print(f"Attempt {i}/5 failed.")
                print(e)
                i += 1
                if not i < 5:
                    raise e
        
Yang's avatar
Yang committed
        # 4.5. if active fire pixels are detected, do fire expansion/merging
screbec's avatar
screbec committed
        if len(afp) > 0:
            # 4. do fire expansion/creation using afp
Yang's avatar
Yang committed
            t_expand = time.time()
Yang's avatar
Yang committed
            allfires = Fire_expand_rtree(allfires, afp, fids_ea)
screbec's avatar
screbec committed
            t_expand2 = time.time()
Yang's avatar
Yang committed
            logger.info(f"expanding fires {(t_expand2-t_expand)}")
Yang's avatar
Yang committed

            # 5. do fire merging using updated fids_ne, fid_ea, fid_sleep
Yang's avatar
Yang committed
            fids_ne = allfires.fids_ne  # new or expanded fires id
            fids_ea = sorted(
                set(fids_ea + allfires.fids_new)
            )  # existing active fires (new fires included)
            fids_sleep = allfires.fids_sleeper
screbec's avatar
screbec committed
            t_merge = time.time()
            if len(fids_ne) > 0:
Yang's avatar
Yang committed
                allfires = Fire_merge_rtree(allfires, fids_ne, fids_ea, fids_sleep)
screbec's avatar
screbec committed
            t_merge2 = time.time()
Yang's avatar
Yang committed
            logger.info(f"merging fires {(t_merge2-t_merge)}")
Yang's avatar
Yang committed
        # # 6. determine or update fire type
        # allfires.updateftypes()
screbec's avatar
screbec committed
        # 7. manualy invalidate static fires (with exceptionally large fire density)
Yang's avatar
Yang committed
        if opt_rmstatfire:
            allfires.invalidate_statfires()
screbec's avatar
screbec committed
        # 8. log and save
        #  - record fid_updated (the fid of fires that change in the time step) to allfires object and logger
Yang's avatar
Yang committed
        logger.info(f"fids_expand: {allfires.fids_expanded}")
        logger.info(f"fids_new: {allfires.fids_new}")
        logger.info(f"fids_merged: {allfires.fids_merged}")
        logger.info(f"fids_invalid: {allfires.fids_invalid}")
Yang's avatar
Yang committed
        # correct heritages at each time step?
        if len(allfires.heritages) > 0:
            allfires.heritages = correct_nested_ids(allfires.heritages)

screbec's avatar
screbec committed
        # 9. loop control
        #  - if t reaches ted, set endloop to True to stop the next loop
Yang's avatar
Yang committed
        if FireTime.t_dif(t, ted) == 0:
screbec's avatar
screbec committed
            endloop = True
Yang's avatar
Yang committed
            # # correct fire heritage of final time step
            # if len(allfires.heritages) > 0:
            #     allfires.heritages = correct_nested_ids(allfires.heritages)
Yang Chen's avatar
Yang Chen committed

        # 10. fire object save
Yang's avatar
Yang committed
        FireIO.save_fobj(allfires, t, region[0], activeonly=False)
        FireIO.save_fobj(allfires, t, region[0], activeonly=True)
Yang's avatar
Yang committed
        # if FireTime.t_dif(t,ted)==0:
        #     FireIO.save_fobj(allfires,t,region[0],activeonly=False)
Yang Chen's avatar
Yang Chen committed

        # 11. update t with the next time stamp
screbec's avatar
screbec committed
        #  - record running times for the loop
        t2 = time.time()
Yang's avatar
Yang committed
        logger.info(f"{(t2-t1)/60.} minutes used to run alg {t}")
screbec's avatar
screbec committed
        t1 = t2
Yang Chen's avatar
Yang Chen committed

        # - move to next time step
Yang's avatar
Yang committed
        t = FireTime.t_nb(t, nb="next")
screbec's avatar
screbec committed
    # record total running time
    t3 = time.time()
Yang's avatar
Yang committed
    logger.info(f"This running takes {(t3-t0)/60.} minutes")
screbec's avatar
screbec committed
    return allfires

screbec's avatar
screbec committed
if __name__ == "__main__":
Yang's avatar
Yang committed
    """ The main code to run time forwarding for a time period
    """
screbec's avatar
screbec committed
    import time
    import FireGpkg
    import FireGpkg_sfs
screbec's avatar
screbec committed
    t1 = time.time()
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    # # set the start and end time
    # tst=(2021,7,13,'AM')
    # ted=(2021,9,15,'PM')
    # region = ('Dixie',[-121.6,39.8,-120.5,40.6])

    tst = (2020, 9, 25, 'PM')
    ted = (2020, 12, 31, "PM")
    #region = ("Creek", [-119.5, 36.8, -118.9, 37.7])
    #region = ('CA',[-124.409591, 32.534155999999996, -114.131211, 42.009518])
    #region = ('Thomas',[-119.79311723013566,34.162521752180936,-118.87850541372941,34.791948775281746])
    #region = ('Meyers',[-113.77550545807375,45.84172304592036,-113.426689540105,46.13941078007829])
    #region = ('Bighorn',[-111.2483396974153,32.107921771038576,-110.2980223146028,32.73852603996812])
    #region = ('Frye',[-110.02849137185659,32.568462386228475,-109.69752823709096,32.84117803581184])
    #region = ('Fish',[-117.98761271684656,34.14492745779149,-117.9021253511239,34.21536501245526])
    #region = ('GrizzlyCreek',[-107.32359115976944,39.51527120096794,-107.04481308359756,39.698839413262284])
    region = ('WesternUS_REDO',[-125.698046875,31.676476158707615,-101.00078125,49.51429477264348])
    #region = ('HermitsPeakREDO',[-105.62745646198083,35.373429737675505,-105.18251017291833,36.26028722617026])
    # region = ('CONUS',[-126.401171875,-61.36210937500001,24.071240929282325,49.40003415463647])
    #region = ('Caldor',[-120.69305873258455,38.52600288552201,-119.90341639860017,38.916006495378696])
screbec's avatar
screbec committed
    # Run the time forward and record daily fire objects .pkl data and fire attributes .GeoJSON data
    print("----------------------------------------")
    print("Running Fire_Forward")
    print("----------------------------------------")
gsfc_landslides's avatar
gsfc_landslides committed
    #Fire_Forward(tst=tst, ted=ted, restart=True, region=region)
screbec's avatar
screbec committed

    # calculate and save snapshot files
    print("----------------------------------------")
    print("Running save_gdf_trng")
    print("----------------------------------------")
gsfc_landslides's avatar
gsfc_landslides committed
    #FireGpkg.save_gdf_trng(tst=tst, ted=ted, regnm=region[0])

    # calculate and save single fire files
    print("----------------------------------------")
    print("Running save_sfts_trng")
    print("----------------------------------------")
    FireGpkg_sfs.save_sfts_trng(tst, ted, regnm=region[0])
screbec's avatar
screbec committed
    t2 = time.time()
Yang's avatar
Yang committed
    print(f"{(t2-t1)/60.} minutes used to run code")