Skip to content
Snippets Groups Projects
FireObj.py 27.9 KiB
Newer Older
screbec's avatar
screbec committed
""" FireObj
Yang's avatar
Yang committed
This is the module defining fire objects used for fire tracking
screbec's avatar
screbec committed

Yang's avatar
Yang committed
FOUR LAYERS OF OBJECTS
screbec's avatar
screbec committed
    a. Allfires:  the class of all fire events
    b. Fire:      the class of a fire event
    c. Cluster:   the class of active fire pixel cluster (only for supporting)
    d. FirePixel: the class of an active fire pixel
"""

# a. Object - Allfires
class Allfires:
    """ Class of allfire events at a particular time step
    """

    # initilization
Yang's avatar
Yang committed
    def __init__(self, t):
        """ Initiate the object with current time
screbec's avatar
screbec committed
        Parameters
        ----------
        t : tuple, (int,int,int,str)
            the year, month, day and 'AM'|'PM'
Yang's avatar
Yang committed
        """
screbec's avatar
screbec committed

Yang's avatar
Yang committed
        # Allfires object has a time stamp
        import FireTime
Yang's avatar
Yang committed
        # self.t = FireTime.t_nb(t,nb='previous') # initialize the object at the previous time step
        self.t = t

        # Allfires object contains a dict of Fire objects with fireID as the key (will be added after reading active fire data)
        self.fires = {}
screbec's avatar
screbec committed

Yang's avatar
Yang committed
        # Initiate lists storing fire ids which will changes at the current time step
Yang's avatar
Yang committed
        self.fids_expanded = (
            []
        )  # a list of ids for fires with expansion at current time step
        self.fids_new = (
            []
        )  # a list of ids for all new fires formed at current time step
        self.fids_merged = (
            []
        )  # a list of ids for fires with merging at current time step
        self.fids_invalid = (
            []
        )  # a list of ids for fires invalidated at current time step
screbec's avatar
screbec committed

        # cumulative recordings
Yang's avatar
Yang committed
        self.heritages = []  # a list of fire heritage relationships (source, target)
        self.id_dict = (
            []
        )  # this list relates the list position of the fire in the allfires object to the fire id
        # (list_index, fireid) (list position can be variable when writing out only active fires)
screbec's avatar
screbec committed

    # properties
    @property
    def cday(self):
Yang's avatar
Yang committed
        """ Datetime date of current time step
        """
screbec's avatar
screbec committed
        from datetime import date
screbec's avatar
screbec committed
        return date(*self.t[:-1])

    @property
    def ampm(self):
Yang's avatar
Yang committed
        """ Ampm indicator of current time step
screbec's avatar
screbec committed
        Parameters
        ----------
        ampm : str, 'AM'|'PM'
           the ampm option calculated from t
Yang's avatar
Yang committed
        """
screbec's avatar
screbec committed
        return self.t[-1]

Yang's avatar
Yang committed
    @property
    def fids(self):
Yang's avatar
Yang committed
        """ List of fire ids
        """
        return [i for i, f in self.fires.items()]
Yang's avatar
Yang committed

screbec's avatar
screbec committed
    @property
    def number_of_fires(self):
Yang's avatar
Yang committed
        """ Total number of fires (active and inactive) at this time step
        """
screbec's avatar
screbec committed
        return len(self.fires)

    @property
    def fids_active(self):
Yang's avatar
Yang committed
        """ List of active fire ids
        """
        return [i for i, f in self.fires.items() if f.isactive]
screbec's avatar
screbec committed
    @property
    def number_of_activefires(self):
Yang's avatar
Yang committed
        """ Total number of active fires at this time step
        """
screbec's avatar
screbec committed
        return len(self.fids_active)

    @property
    def activefires(self):
Yang's avatar
Yang committed
        """ dict of active fires
        """
        return {i: f for i, f in self.fires.items() if f.isactive}
Yang's avatar
Yang committed

    @property
    def mayactivefires(self):
Yang's avatar
Yang committed
        """ dict of active fires and sleepers
        """
        return {i: f for i, f in self.fires.items() if (f.isactive or f.mayreactivate)}
Yang's avatar
Yang committed

    @property
    def deadfires(self):
Yang's avatar
Yang committed
        """ dict of inactive fires not going to be reactivated
        """
        return {
            i: f for i, f in self.fires.items() if not (f.isactive or f.mayreactivate)
        }
Yang's avatar
Yang committed

Yang's avatar
Yang committed
    @property
    def fids_dead(self):
Yang's avatar
Yang committed
        """ List of fire ids that is not going to be reactivated
        """
        return [i for i, f in self.fires.items() if not (f.isactive or f.mayreactivate)]
Yang's avatar
Yang committed

Yang's avatar
Yang committed
    @property
    def fids_sleeper(self):
Yang's avatar
Yang committed
        """ List of fire ids that may reactivate
        """
        return [i for i, f in self.fires.items() if f.mayreactivate]
Yang's avatar
Yang committed

    @property
    def number_of_sleeper(self):
Yang's avatar
Yang committed
        """ Total number of sleep fires at this time step
        """
Yang's avatar
Yang committed
        return len(self.fids_sleeper)
screbec's avatar
screbec committed

    @property
    def fids_valid(self):
Yang's avatar
Yang committed
        """ List of valid (non-invalid) fire ids
        """
        # return [self.fires[f].fireID for f in self.fires if self.fires[f].invalid is False]
Yang's avatar
Yang committed
        return [i for i, f in self.fires.items() if f.invalid is False]
screbec's avatar
screbec committed

    @property
    def number_of_validfires(self):
Yang's avatar
Yang committed
        """ Total number of valid fires at this time step
        """
screbec's avatar
screbec committed
        return len(self.fids_valid)

    @property
    def validfires(self):
Yang's avatar
Yang committed
        """ List of valid fires
        """
Yang's avatar
Yang committed
        # return [self.fires[fid] for fid in self.fids_valid]
Yang's avatar
Yang committed
        return {i: f for i, f in self.fires.items() if f.invalid is False}
screbec's avatar
screbec committed

    @property
    def fids_updated(self):
Yang's avatar
Yang committed
        """ List of fire id which is updated at this time step
screbec's avatar
screbec committed
            (expanded, new, merged, invalid)
Yang's avatar
Yang committed
        """
        fids_updated = list(
            set(
                self.fids_expanded
                + self.fids_new
                + self.fids_merged
                + self.fids_invalid
            )
        )
screbec's avatar
screbec committed
        return fids_updated

    @property
    def fids_ne(self):
Yang's avatar
Yang committed
        """ List of fire id which is newly formed or expanded
screbec's avatar
screbec committed
               at this time step
Yang's avatar
Yang committed
        """
        fids_ne = sorted(set(self.fids_expanded + self.fids_new))
screbec's avatar
screbec committed
        return fids_ne

    # functions to be run before tracking VIIRS active fire pixels at each time step
Yang's avatar
Yang committed
    def update_t(self, t):
        """ Update the time (cday and ampm) for the Allfire object.
screbec's avatar
screbec committed
        Parameters
        ----------
        t : tuple, (int,int,int,str)
            the year, month, day and 'AM'|'PM'
Yang's avatar
Yang committed
        """
screbec's avatar
screbec committed
        self.t = list(t)  # current date and ampm

Yang's avatar
Yang committed
    def update_t_allfires(self, t):
        """ Update the time (t) for each Fire object in the Allfire object.
screbec's avatar
screbec committed
        Parameters
        ----------
        t : tuple, (int,int,int,str)
            the year, month, day and 'AM'|'PM'
Yang's avatar
Yang committed
        """
        for i, f in self.fires.items():
screbec's avatar
screbec committed
            f.t = list(t)

    def reset_newpixels(self):
Yang's avatar
Yang committed
        """ Reset newpixels to [] for each fire.
        """
        for i, f in self.fires.items():
screbec's avatar
screbec committed
            f.newpixels = []

Yang's avatar
Yang committed
    def cleanup(self, t):
        """ Clean up Allfires obj at each time step
Yang's avatar
Yang committed
        - update t (for allfires and all fires)
        - clean up lists to record fire changes
        - clean up newpixels for each fire
Yang's avatar
Yang committed
        """
Yang's avatar
Yang committed
        # time updated to t
Yang's avatar
Yang committed
        self.update_t(t)  # update t for allfires
Yang's avatar
Yang committed
        self.update_t_allfires(t)  # update t

        # reset the fids used to record changes
Yang's avatar
Yang committed
        self.fids_expanded = (
            []
        )  # a list of ids for fires with expansion at current time step
        self.fids_new = (
            []
        )  # a list of ids for all new fires formed at current time step
        self.fids_merged = (
            []
        )  # a list of ids for fires with merging at current time step
        self.fids_invalid = (
            []
        )  # a list of ids for fires invalidated at current time step
screbec's avatar
screbec committed

Yang's avatar
Yang committed
        # reset newpixels for each fire to empty list.
Yang's avatar
Yang committed
        for i, f in self.fires.items():
Yang's avatar
Yang committed
            f.newpixels = []

Yang's avatar
Yang committed
    def newyear_reset(self, regnm):
        """ reset fire ids at the start of a new year
        """
Yang Chen's avatar
Yang Chen committed
        # re-id all active fires
Yang's avatar
Yang committed
        newfires = {}
Yang Chen's avatar
Yang Chen committed
        fidmapping = []
Yang's avatar
Yang committed
        fids_keep = self.fids_active + self.fids_sleeper
Yang's avatar
Yang committed
        for i, fid in enumerate(fids_keep):
Yang's avatar
Yang committed
            newfires[i] = self.fires[fid]  # record new fireID and fire object
Yang's avatar
Yang committed
            newfires[i].fireID = i  # also update fireID attribute of fire object
            fidmapping.append((fid, i))
Yang's avatar
Yang committed
        self.fires = newfires

        # lastyearfires = {}
        # fidmapping = []
        # nfid = 0
        # for f in self.activefires:
        #     ofid = f.fireID
        #     f.fireID = nfid
        #     # lastyearfires.append(f)
        #     lastyearfires[nfid] = f
        #     fidmapping.append((ofid,nfid))
        #     nfid += 1
        # self.fires = lastyearfires
Yang Chen's avatar
Yang Chen committed

        # clean heritages
        self.heritages = []

        # save the mapping table
        if len(fidmapping) > 0:
            FireIO.save_newyearfidmapping(fidmapping, self.t[0], regnm)
screbec's avatar
screbec committed
    # functions to be run after tracking VIIRS active fire pixels at each time step
Yang's avatar
Yang committed
    def record_fids_change(
        self, fids_expanded=None, fids_new=None, fids_merged=None, fids_invalid=None
    ):
        """ Update the list of ids for fires with changes at current time step.
screbec's avatar
screbec committed
        Parameters
        ----------
        fids_expanded : list
            ids of expanded fires
        fids_new : list
            ids of new formed fires
        fids_merged : list
            ids of fires with other fires merging to them
        fids_invalid : list
            ids of fires invalidated (due to merging with other fires)
Yang's avatar
Yang committed
        """
screbec's avatar
screbec committed
        if fids_expanded:
Yang's avatar
Yang committed
            self.fids_expanded = fids_expanded  # expanded fires
screbec's avatar
screbec committed
        if fids_new:
Yang's avatar
Yang committed
            self.fids_new = fids_new  # new formed fires
screbec's avatar
screbec committed
        if fids_merged:
Yang's avatar
Yang committed
            self.fids_merged = fids_merged  # fires with merging with other fires
screbec's avatar
screbec committed
        if fids_invalid:
Yang's avatar
Yang committed
            self.fids_invalid = (
                fids_invalid  # fires invalidated due to merging with other fires
            )
screbec's avatar
screbec committed

    def invalidate_statfires(self):
Yang's avatar
Yang committed
        """ If pixel density of an active fire is too large, assume it's static
screbec's avatar
screbec committed
                fires and invalidate it.
Yang's avatar
Yang committed
        """
        try:
            for i, f in self.activefires.items():
                if (f.pixden > 20) & (f.farea < 20):
                    # invalidate the fire
                    f.invalid = True

                    # add the fire id into the fids_invalid list
                    self.fids_invalid.append(f.fireID)
                    print('one static fire invalidated')
        except Exception as e:
            print(e)
Yang Chen's avatar
Yang Chen committed

    # def updateLCTmax(self):
    #     ''' update Land cover type (dominant LCT for all fire pixels) for active
    #             fires that is small in size (<1000 pixels)
    #     '''
    #     import FireIO
    #     for f in self.activefires:
    #         if (f.n_pixels < 1000):
    #             # get all LCT for the fire pixels
    #             vLCT = FireIO.get_LCT(f.locs)
    #             # extract the LCT with most pixel counts
    #             LCTmax = max(set(vLCT), key = vLCT.count)
    #             f.LCTmax = LCTmax

Yang's avatar
Yang committed
    # def updateftypes(self):
    #     ''' update fire type and dominant LCT for active fires
    #     '''
    #     import FireFuncs
    #     for f in self.activefires:
    #         if f.n_newpixels > 0:
    #             f.ftype = FireFuncs.set_ftype(f)
Yang's avatar
Yang committed
    # f.set_ftype()

screbec's avatar
screbec committed

# b. Object - Fire
class Fire:
    """ Class of a single fire event at a particular time step
    """

    # initilization
Yang's avatar
Yang committed
    def __init__(self, id, t, pixels, sensor="viirs"):
        """ Initialize Fire class with active fire pixels locations and t. This
screbec's avatar
screbec committed
                is only called when fire clusters forming a new Fire object.
        Parameters
        ----------
        id : int
            fire id
        t : tuple, (int,int,int,str)
            the year, month, day and 'AM'|'PM'
        pixels : list (nx5)
            latitude, longitude, line, sample, and FRP values of active fire pixels
Yang's avatar
Yang committed
        sensor : str
            the remote sensing instrument, 'viirs' | 'modis'; no differentiation
            between SNPP and NOAA20
Yang's avatar
Yang committed
        """
        import FireVector

Yang's avatar
Yang committed
        # initialize fire id and sensor
Yang Chen's avatar
Yang Chen committed
        self.fireID = id
Yang's avatar
Yang committed
        self.mergeid = id  # mergeid is the final fire id the current fire being merged; use current fire id at initialization
        self.sensor = sensor  #
screbec's avatar
screbec committed

        # initialize current time, fire start time, and fire final time
Yang's avatar
Yang committed
        tlist = list(t)  # convert (y,m,d,ampm) to [y,m,d,ampm]
screbec's avatar
screbec committed
        self.t = tlist  # current time
        self.t_st = tlist
        self.t_ed = tlist

        # initialize pixels
Yang Chen's avatar
Yang Chen committed
        # fpixels = [FirePixel((p[0],p[1]),(p[2],p[3],p[4]),tlist,id) for p in pixels]
        fpixels = pixels
Yang's avatar
Yang committed
        self.pixels = fpixels  # all pixels
        self.newpixels = fpixels  # new detected pixels
Yang's avatar
Yang committed
        # self.actpixels = fpixels   # new detected pixels of last active fire detection
Yang's avatar
Yang committed
        self.ignpixels = fpixels  # pixels at ignition
screbec's avatar
screbec committed

        # initialize hull using the pixels
        locs = [p.loc for p in fpixels]  # list of [lat,lon]
Yang's avatar
Yang committed
        # locs_geo = [p.loc_geo for p in fpixels]  # list of [lat,lon]
Yang's avatar
Yang committed
        hull = FireVector.cal_hull(locs, sensor)  # the hull from all locs
        self.hull = hull  # note fire.hull is not automatically updated (need explicit calculation if changes occur)
screbec's avatar
screbec committed

        # initialize the exterior pixels (pixels within the inward extbuffer of
        #    the hull; used for saving time for hull calculation of large fires)
Yang's avatar
Yang committed
        self.extpixels = FireVector.cal_extpixels(fpixels, hull)
screbec's avatar
screbec committed

Yang's avatar
Yang committed
        # fline of latest active timestep, used for sleeper threshold
        self.fline_prior = None

screbec's avatar
screbec committed
        # always set validate at initialization
        self.invalid = False

        # get and record fm1000 value at ignition (at the time of initilization)
        # self.stFM1000 = FireIO.get_stFM1000(hull,locs,tlist)

        # LCTmax
        # vLCT = FireIO.get_LCT(locs)
        # self.LCTmax = max(set(vLCT), key = vLCT.count)

    # properties
    @property
    def cday(self):
Yang's avatar
Yang committed
        """ Current day (datetime date)
        """
screbec's avatar
screbec committed
        from datetime import date
screbec's avatar
screbec committed
        return date(*self.t[:-1])
    @property
    def cdoy(self):
Yang's avatar
Yang committed
        """ Current day (datetime date)
        """
        return self.cday.timetuple().tm_yday
screbec's avatar
screbec committed

    @property
    def ampm(self):
Yang's avatar
Yang committed
        """ Current ampm flag, 'AM'|'PM'
        """
screbec's avatar
screbec committed
        return self.t[-1]

    @property
    def duration(self):
Yang's avatar
Yang committed
        """ Time difference between first and last active fire detection
        """
Yang's avatar
Yang committed
        import FireTime
Yang's avatar
Yang committed

        duration = FireTime.t_dif(self.t_st, self.t_ed)  # + 0.5
screbec's avatar
screbec committed
        return duration

    @property
    def t_inactive(self):
Yang's avatar
Yang committed
        """ Time difference between current time and the last active fire detection
        """
Yang's avatar
Yang committed
        import FireTime
Yang's avatar
Yang committed

        t_inactive = FireTime.t_dif(self.t_ed, self.t)
screbec's avatar
screbec committed
        return t_inactive

    @property
    def isactive(self):
Yang's avatar
Yang committed
        """ Fire active status
        """
        from FireConsts import maxoffdays

screbec's avatar
screbec committed
        # invalidated fires are always inactive
        if self.invalid:
            return False
        # otherwise, set to True if no new pixels detected for 5 consecutive days
Yang's avatar
Yang committed
        return self.t_inactive <= maxoffdays
    @property
Yang's avatar
Yang committed
    def isdead(self):
Yang's avatar
Yang committed
        """ Fire active status
        """
Yang's avatar
Yang committed
        from FireConsts import limoffdays

        # invalidated fires are always inactive
        if self.invalid:
            return False
        # otherwise, set to True if no new pixels detected for 5 consecutive days
Yang's avatar
Yang committed
        return self.t_inactive > limoffdays
Yang's avatar
Yang committed

    @property
    def mayreactivate(self):
Yang's avatar
Yang committed
        """ Fire sleeper status
        """
        from FireConsts import maxoffdays, limoffdays
        # invalidated fires are always inactive
        if self.invalid:
            return False
        # otherwise, set to True if no new pixels detected for 5 consecutive days
Yang's avatar
Yang committed
        return maxoffdays < self.t_inactive <= limoffdays
    @property
    def isignition(self):
Yang's avatar
Yang committed
        """ Is the current timestep the ignition?
Yang's avatar
Yang committed
        when start time == end time; and new pixel > 0
Yang's avatar
Yang committed
        """
Yang's avatar
Yang committed
        import FireTime
Yang's avatar
Yang committed

        if (
            len(self.newpixels) == 0
        ):  # in this case t_st = t_ed because t_ed has not been updated
Yang's avatar
Yang committed
            ign = 0
        else:
Yang's avatar
Yang committed
            ign = (FireTime.t_dif(self.t_st, self.t_ed) == 0) * 1
screbec's avatar
screbec committed
    @property
    def locs(self):
Yang's avatar
Yang committed
        """ List of fire pixel locations (lat,lon)
        """
screbec's avatar
screbec committed
        return [p.loc for p in self.pixels]

Yang's avatar
Yang committed
    @property
    def locs_geo(self):
Yang's avatar
Yang committed
        """ List of fire pixel locations (lat,lon)
        """
Yang's avatar
Yang committed
        return [p.loc_geo for p in self.pixels]

Yang's avatar
Yang committed
    @property
    def locsMP(self):
Yang's avatar
Yang committed
        """ MultiPoint shape of locs
        """
Yang's avatar
Yang committed
        from shapely.geometry import MultiPoint
Yang's avatar
Yang committed

        mp = MultiPoint([(p[0], p[1]) for p in self.locs])
Yang's avatar
Yang committed
        return mp

screbec's avatar
screbec committed
    @property
    def n_pixels(self):
Yang's avatar
Yang committed
        """ Total number of fire pixels"""
screbec's avatar
screbec committed
        return len(self.pixels)

    @property
    def newlocs(self):
Yang's avatar
Yang committed
        """ List of new fire pixels locations (lat,lon)
        """
screbec's avatar
screbec committed
        return [p.loc for p in self.newpixels]

Yang's avatar
Yang committed
    @property
    def newlocs_geo(self):
Yang's avatar
Yang committed
        """ List of new fire pixels locations (lat,lon)
        """
Yang's avatar
Yang committed
        return [p.loc_geo for p in self.newpixels]

Yang Chen's avatar
Yang Chen committed
    @property
    def newlocsMP(self):
Yang's avatar
Yang committed
        """ MultiPoint shape of newlocs
        """
Yang's avatar
Yang committed
        from shapely.geometry import MultiPoint
Yang's avatar
Yang committed

        mp = MultiPoint([(p[0], p[1]) for p in self.newlocs])
Yang's avatar
Yang committed
        return mp
    @property
    def newpixelatts(self):
Yang's avatar
Yang committed
        """ List of new fire pixels attributes
        """
        return [
            (p.lon, p.lat, p.frp, p.DS, p.DT, p.datetime, p.ampm, p.sat)
            for p in self.newpixels
        ]
screbec's avatar
screbec committed
    @property
    def n_newpixels(self):
Yang's avatar
Yang committed
        """ Total number of new fire pixels
        """
screbec's avatar
screbec committed
        return len(self.newpixels)

    @property
    def extlocs(self):
Yang's avatar
Yang committed
        """ List of exterior fire pixel locations (lat,lon)
        """
screbec's avatar
screbec committed
        return [p.loc for p in self.extpixels]

Yang's avatar
Yang committed
    @property
    def extlocsMP(self):
Yang's avatar
Yang committed
        """ MultiPoint shape of extlocs
        """
Yang's avatar
Yang committed
        from shapely.geometry import MultiPoint
Yang's avatar
Yang committed

        mp = MultiPoint([(p[0], p[1]) for p in self.extlocs])
Yang's avatar
Yang committed
        return mp

screbec's avatar
screbec committed
    @property
    def n_extpixels(self):
Yang's avatar
Yang committed
        """ Total number of exterior fire pixels
        """
screbec's avatar
screbec committed
        return len(self.extpixels)

    @property
    def ignlocs(self):
Yang's avatar
Yang committed
        """ List of fire pixel locations (lat,lon) at ignition time step
        """
screbec's avatar
screbec committed
        return [p.loc for p in self.ignpixels]

Yang's avatar
Yang committed
    @property
    def ignlocsMP(self):
Yang's avatar
Yang committed
        """ MultiPoint shape of ignlocs
        """
Yang's avatar
Yang committed
        from shapely.geometry import MultiPoint
Yang's avatar
Yang committed

        mp = MultiPoint([(p[0], p[1]) for p in self.ignlocs])
Yang's avatar
Yang committed
        return mp

    @property
    def ignlocs_geo(self):
Yang's avatar
Yang committed
        """ List of fire pixel locations (lat,lon) at ignition time step
        """
Yang's avatar
Yang committed
        return [p.loc_geo for p in self.ignpixels]

    @property
    def ignlocsMP_geo(self):
Yang's avatar
Yang committed
        """ MultiPoint shape of ignlocs
        """
Yang's avatar
Yang committed
        from shapely.geometry import MultiPoint
Yang's avatar
Yang committed

        mp = MultiPoint([(p[0], p[1]) for p in self.ignlocs_geo])
Yang's avatar
Yang committed
        return mp

screbec's avatar
screbec committed
    @property
    def n_ignpixels(self):
Yang's avatar
Yang committed
        """ Total number of ignition fire pixels
        """
screbec's avatar
screbec committed
        return len(self.ignpixels)

    @property
    def farea(self):
Yang's avatar
Yang committed
        """ Fire spatail size of the fire event (km2)
        """
        from FireConsts import area_VI
Yang's avatar
Yang committed
        import numpy as np
screbec's avatar
screbec committed
        # get hull
        fhull = self.hull

        # If no hull, return area calculated from number of pixels
        if fhull is None:
            return self.n_pixels * area_VI
        # otherwise, use calConcHarea to calculate area,
Yang's avatar
Yang committed
        #   but no smaller than area_VI (sometimes calculated hull area is very small)
screbec's avatar
screbec committed
        else:
Yang's avatar
Yang committed
            # from pyproj import Geod
            # geod = Geod(ellps="WGS84")
            # area_cal = np.abs(geod.geometry_area_perimeter(self.hull)[0]/1e6)
Yang's avatar
Yang committed
            area_cal = fhull.area / 1e6
            return max(area_cal, area_VI)
Yang's avatar
Yang committed

            # import FireVector
            # return max(FireVector.calConcHarea(fhull),area_VI)

Yang's avatar
Yang committed
    # @property
    # def centroid(self):
    #     ''' Centroid of fire object (lat,lon)
    #     '''
    #     import FireClustering
    #
    #     # get hull
    #     fhull = self.hull
    #
    #     if fhull is not None: # centroid of the hull
    #         cent = (fhull.centroid.y,fhull.centroid.x)
    #     else: # when no hull, use the centroid of all pixels
    #         cent = FireClustering.cal_centroid(self.locs)
    #     return cent
screbec's avatar
screbec committed

    @property
    def pixden(self):
Yang's avatar
Yang committed
        """ Fire pixel density (number of pixels per km2 fire area)
        """
screbec's avatar
screbec committed
        farea = self.farea
        if farea > 0:
Yang's avatar
Yang committed
            return self.n_pixels / farea
screbec's avatar
screbec committed
        else:
            return 0

    @property
    def meanFRP(self):
Yang's avatar
Yang committed
        """ Mean FRP of the new fire pixels
        """
Yang Chen's avatar
Yang Chen committed
        frps = [p.frp for p in self.newpixels]
screbec's avatar
screbec committed
        if len(frps) > 0:
Yang's avatar
Yang committed
            m = sum(frps) / len(frps)
screbec's avatar
screbec committed
        else:
            m = 0
        return m

    @property
    def ftypename(self):
Yang's avatar
Yang committed
        """ Fire type name
        """
Yang's avatar
Yang committed
        import FireFuncs
Yang's avatar
Yang committed
        return FireFuncs.set_ftypename(self)
screbec's avatar
screbec committed

    @property
    def fperim(self):
Yang's avatar
Yang committed
        """ Perimeter length of fire hull
        """
screbec's avatar
screbec committed
        # get hull
        fhull = self.hull

        if fhull is None:  # if no hull, return zero
            perim = 0
        else:  # otherwise, use the hull length
Yang's avatar
Yang committed
            # from pyproj import Geod
            # geod = Geod(ellps="WGS84")
            # perim = geod.geometry_length(fhull)/1000 # in km
Yang's avatar
Yang committed
            perim = fhull.length / 1e3  # km
screbec's avatar
screbec committed
        return perim

    @property
    def flinepixels(self):
Yang's avatar
Yang committed
        """ List of all fire pixels near the fire perimeter (fine line pixels)
        """
screbec's avatar
screbec committed
        from shapely.geometry import Point, MultiLineString
        import FireVector
        from FireConsts import fpbuffer
        # get pixels of last active fire detection
Yang's avatar
Yang committed
        nps = self.newpixels
screbec's avatar
screbec committed
        # get hull
        fhull = self.hull

Yang's avatar
Yang committed
        if fhull is None:  # if no hull, return empty list
screbec's avatar
screbec committed
            return []
        else:
            try:
                # otherwise, extract the pixels nearl the hull
                # if hull is a polygon, return new pixels near the hull
                if fhull.type == "Polygon":
                    # lr = fhull.exterior.buffer(fpbuffer)
                    lr = FireVector.addbuffer(fhull.exterior, fpbuffer)
                    return [p for p in nps if lr.contains(Point(p.loc[0], p.loc[1]))]

                # if hull is a multipolygon, return new pixels near the hull
                elif fhull.type == "MultiPolygon":
                    # mlr = MultiLineString([x.exterior for x in fhull]).buffer(fpbuffer)
                    mlr = MultiLineString([x.exterior for x in fhull])
                    mlr = FireVector.addbuffer(mlr, fpbuffer)
                    return [p for p in nps if mlr.contains(Point(p.loc[0], p.loc[1]))]
            
            except Exception as e:
                print(e)
                return []
screbec's avatar
screbec committed

Yang's avatar
Yang committed
    @property
    def flplocs(self):
Yang's avatar
Yang committed
        """ List of fire line pixel locations (lat,lon)
        """
Yang's avatar
Yang committed
        return [p.loc for p in self.flinepixels]

    @property
    def flplocsMP(self):
Yang's avatar
Yang committed
        """ MultiPoint shape of flplocs
        """
Yang's avatar
Yang committed
        from shapely.geometry import Point
        import geopandas as gpd
Yang's avatar
Yang committed

        mp = [Point(p[0], p[1]) for p in self.flplocs]
Yang's avatar
Yang committed
        return gpd.GeoSeries(mp)

    @property
    def n_flinepixels(self):
Yang's avatar
Yang committed
        """ Total number of fire line pixels
        """
Yang's avatar
Yang committed
        return len(self.flinepixels)

screbec's avatar
screbec committed
    @property
    def fline(self):
Yang's avatar
Yang committed
        """ Active fire line MultiLineString shape (segment of fire perimeter with active fires nearby)
        """
Yang's avatar
Yang committed
        from shapely.geometry import MultiLineString
Yang's avatar
Yang committed
        from FireConsts import flbuffer, VIIRSbuf
        import FireVector
screbec's avatar
screbec committed

Yang's avatar
Yang committed
        if (
            self.n_flinepixels == 0
        ):  # this happens is last active pixels are within the fire scar
screbec's avatar
screbec committed
            return None

Yang's avatar
Yang committed
        # get fireline pixel locations, different from flplocsMP since it contains a VIIRS pixel buffer
Yang's avatar
Yang committed
        flinelocsMP = FireVector.doMultP(self.flplocs, VIIRSbuf)
screbec's avatar
screbec committed

        # get the hull
        fhull = self.hull

        # calculate the fire line
Yang's avatar
Yang committed
        if fhull is None:  # if no hull, return None
screbec's avatar
screbec committed
            return None
        else:  # otherwise, create shape of the active fire line
Yang's avatar
Yang committed
            if fhull.type == "MultiPolygon":
screbec's avatar
screbec committed
                # extract exterior of fire perimeter
                mls = MultiLineString([plg.exterior for plg in fhull])
                # return the part which intersects with  bufferred flinelocsMP
                # return mls.intersection(flinelocsMP.buffer(flbuffer))
Yang's avatar
Yang committed
                flinelocsMP_buf = FireVector.addbuffer(flinelocsMP, flbuffer)
Yang's avatar
Yang committed
                fline = mls.intersection(flinelocsMP_buf)
screbec's avatar
screbec committed

Yang's avatar
Yang committed
            elif fhull.type == "Polygon":
screbec's avatar
screbec committed
                mls = fhull.exterior
                # return mls.intersection(flinelocsMP.buffer(flbuffer))
Yang's avatar
Yang committed
                flinelocsMP_buf = FireVector.addbuffer(flinelocsMP, flbuffer)
Yang's avatar
Yang committed
                fline = mls.intersection(flinelocsMP_buf)
screbec's avatar
screbec committed
            else:  # if fhull type is not 'MultiPolygon' or 'Polygon', return flinelocsMP
Yang's avatar
Yang committed
                fline = flinelocsMP

            # we save the fire line to a new property (this is only updated when fline not None)
            self.fline_prior = fline

            return fline
screbec's avatar
screbec committed

    @property
    def flinelen(self):
Yang's avatar
Yang committed
        """ The length of active fire line
        """
Yang's avatar
Yang committed
        from pyproj import Geod
screbec's avatar
screbec committed
        try:
Yang's avatar
Yang committed
            flinelen = self.fline.length / 1e3  # km
Yang's avatar
Yang committed
            # geod = Geod(ellps="WGS84")
            # flinelen = geod.geometry_length(self.fline)/1000 # in km
screbec's avatar
screbec committed
        except:
            flinelen = 0

        return flinelen

Yang's avatar
Yang committed
    # functions
    def updateftype(self):
Yang's avatar
Yang committed
        """ Update fire type
Yang's avatar
Yang committed
        # do not use ftype as property since it may mess up when t updates (without pixel addition)
Yang's avatar
Yang committed
        """
Yang's avatar
Yang committed
        import FireFuncs
Yang's avatar
Yang committed
        self.ftype = FireFuncs.set_ftype(self)

Yang's avatar
Yang committed
    def updatefhull(self, newlocs):
        """ Update the hull using old hull and new locs
        """
Yang's avatar
Yang committed
        import FireVector
Yang's avatar
Yang committed

        hull = FireVector.cal_hull(newlocs, sensor=self.sensor)
Yang's avatar
Yang committed
        # use the union to include hull in past time step
        phull = self.hull
        self.hull = phull.union(hull)

Yang's avatar
Yang committed
    def updateextpixels(self, newpixels):
        """ Update the external pixels
        """
Yang's avatar
Yang committed
        import FireVector
Yang's avatar
Yang committed
        pextpixels = self.extpixels
Yang's avatar
Yang committed
        self.extpixels = FireVector.cal_extpixels(pextpixels + newpixels, self.hull)

Yang's avatar
Yang committed

screbec's avatar
screbec committed
# c. Object - Cluster
class Cluster:
    """ class of active fire pixel cluster at a particular time
    """

    # initilization
Yang's avatar
Yang committed
    def __init__(self, id, pixels, t, sensor="viirs"):
        """ initilization
screbec's avatar
screbec committed

        Parameters
        ----------
        id : int
            cluster id number
        pixels : 3-element list
            (lat, lon, FRP) of AF pixels
        t : tuple, (int,int,int,str)
            the year, month, day and 'AM'|'PM'
Yang's avatar
Yang committed
        """
screbec's avatar
screbec committed
        from datetime import date
screbec's avatar
screbec committed
        self.cday = date(*t[:-1])  # current date
Yang's avatar
Yang committed
        self.ampm = t[-1]  # current ampm
screbec's avatar
screbec committed
        self.id = id
        self.sensor = sensor
Yang's avatar
Yang committed
        self.pixels = pixels  # (x,y,FRP,...)
screbec's avatar
screbec committed

    # properties
    @property
    def locs(self):
Yang's avatar
Yang committed
        """ List of pixel locations (lat,lon)
        """
        return [(p.x, p.y) for p in self.pixels]
screbec's avatar
screbec committed

    @property
    def centroid(self):
Yang's avatar
Yang committed
        """ Centroid of the cluster (lat, lon)
        """
        import FireClustering
screbec's avatar
screbec committed
        return FireClustering.cal_centroid(self.locs)

    @property
    def n_pixels(self):
Yang's avatar
Yang committed
        """ Number of total pixels
        """
screbec's avatar
screbec committed
        return len(self.pixels)

    @property
    def hull(self):
Yang's avatar
Yang committed
        """ Fire concave hull (alpha shape)
        """
        import FireVector

        hull = FireVector.cal_hull(self.locs, self.sensor)
screbec's avatar
screbec committed
        return hull
    @property
    def b_box(self):
Yang's avatar
Yang committed
        """ Bounding box of concave hull
        """
        b_box = self.hull.bounds
        return b_box
screbec's avatar
screbec committed

screbec's avatar
screbec committed
# d. Object - FirePixel
class FirePixel:
    """ class of an acitve fire pixel, which includes
        loc : location (lat, lon)
        atts : line & sample or viirs pixel, fire radiative power
screbec's avatar
screbec committed
        t : time (y,m,d,ampm) of record
        origin : the fire id originally recorded (before merging)
    """
Yang's avatar
Yang committed

    def __init__(self, x, y, lon, lat, frp, DS, DT, ampm, t, Sat, origin):
Yang's avatar
Yang committed
        self.x = x
        self.y = y
Yang Chen's avatar
Yang Chen committed
        self.lat = lat
        self.lon = lon
Yang's avatar
Yang committed
        self.frp = frp  # frp
        # self.t = list(t)      # (year,month,day,ampm)
        self.DS = DS
        self.DT = DT
Yang's avatar
Yang committed
        self.ampm = ampm
Yang's avatar
Yang committed
        self.datetime = t  # YYYYMMDD_HHMM
        self.sat = Sat  # satellite
        self.origin = origin  # intially assigned fire id

Yang Chen's avatar
Yang Chen committed
    @property
    def loc(self):
Yang's avatar
Yang committed
        return (self.x, self.y)
Yang's avatar
Yang committed

    @property
    def loc_geo(self):
Yang's avatar
Yang committed
        return (self.lon, self.lat)