Пример #1
0
    def _process_row(self, row):
        fire_id = row.get("id") or str(uuid.uuid4())
        if fire_id not in self._fires:
            self._fires[fire_id] = Fire({
                "id":
                fire_id,
                "event_of": {},
                "specified_points_by_date":
                defaultdict(lambda: [])
            })

        start, utc_offset = self._parse_date_time(row["date_time"])
        if not start:
            raise ValueError("Fire location missing time information")

        sp = {
            loc_attr: f(row.get(csv_key))
            for csv_key, loc_attr, f in LOCATION_FIELDS
        }
        if utc_offset:
            sp['utc_offset'] = utc_offset
        self._fires[fire_id]['specified_points_by_date'][start].append(sp)

        # event and type could have been set when the Fire object was
        # instantiated, but checking amd setting here allow the fields to
        # be set even if only subsequent rows for the fire have them defined
        if row.get("event_id"):
            self._fires[fire_id]["event_of"]["id"] = row["event_id"]
        if row.get("event_url"):
            self._fires[fire_id]["event_of"]["url"] = row["event_url"]

        if row.get("type"):
            self._fires[fire_id]["type"] = row["type"].lower()
Пример #2
0
def generate_dummy_fire(model_start, num_hours, grid_params):
    """Returns dummy fire formatted like
    """
    logging.info("Generating dummy fire for HYSPLIT")
    f = Fire(
        # let fire autogenerate id
        area=1,
        latitude=grid_params['center_latitude'],
        longitude=grid_params['center_longitude'],
        # TODO: look up offset from lat, lng, and model_start
        utc_offset=0,  # since plumerise and timeprofile will have utc keys
        plumerise={},
        timeprofile={},
        emissions={
            p: {e: DUMMY_EMISSIONS_VALUE
                for e in DUMMY_EMISSIONS}
            for p in PHASES
        })
    for hour in range(num_hours):
        dt = model_start + datetime.timedelta(hours=hour)
        dt = dt.strftime('%Y-%m-%dT%H:%M:%S')
        f['plumerise'][dt] = DUMMY_PLUMERISE_HOUR
        f['timeprofile'][dt] = dummy_timeprofile_hour(num_hours)

    return f
Пример #3
0
def generate_dummy_fire(model_start, num_hours, grid_params):
    """Returns dummy fire formatted like
    """
    logging.info("Generating dummy fire for HYSPLIT")
    f = Fire(
        is_dummy=True,
        # let fire autogenerate id
        area=1,
        latitude=grid_params['center_latitude'],
        longitude=grid_params['center_longitude'],
        # TODO: look up offset from lat, lng, and model_start
        utc_offset=0,  # since plumerise and timeprofile will have utc keys
        plumerise={},
        timeprofiled_emissions={},
        timeprofiled_area={})
    hourly_area = 1.0 / float(num_hours)
    hourly_timeprofiled_emissions = dummy_timeprofiled_emissions_hour()
    for hour in range(num_hours):
        dt = model_start + datetime.timedelta(hours=hour)
        dt = dt.strftime('%Y-%m-%dT%H:%M:%S')
        f['plumerise'][dt] = DUMMY_PLUMERISE_HOUR
        f['timeprofiled_area'][dt] = hourly_area
        f['timeprofiled_emissions'][dt] = hourly_timeprofiled_emissions

    return f
Пример #4
0
 def test_two_fires(self):
     fires = [
         Fire({
             'activity': [{
                 "location": {
                     "area": 10
                 },
                 "fuelbeds": [{
                     "fccs_id": "1",
                     "pct": 30
                 }, {
                     "fccs_id": "2",
                     "pct": 70
                 }]
             }]
         }),
         Fire({
             'activity': [{
                 "location": {
                     "area": 5
                 },
                 "fuelbeds": [{
                     "fccs_id": "2",
                     "pct": 10
                 }, {
                     "fccs_id": "3",
                     "pct": 90
                 }]
             }]
         })
     ]
     expected_summary = [{
         "fccs_id": "1",
         "pct": 20
     }, {
         "fccs_id": "2",
         "pct": 50
     }, {
         "fccs_id": "3",
         "pct": 30
     }]
     summary = fuelbeds.summarize(fires)
     assert summary == expected_summary
Пример #5
0
    def load(self):
        data = self._load()
        self._save_copy(data)

        fires = self._marshal(data)
        # cast each fire to Fire object, in case child class
        #   did override _marshal but didn't return Fire objects?
        # TODO: support config setting 'skip_failures'
        fires = [Fire(f) for f in fires]
        fires = self._prune(fires)

        return fires
Пример #6
0
 def test_two_fires(self):
     fires = [
         Fire({
             'activity':[{
                 "active_areas":[{
                     "specified_points": [{
                         "area": 10,
                         "lat": 45,
                         "lng": -118,
                         "fuelbeds":[
                             {"fccs_id": "1", "pct": 30},
                             {"fccs_id": "2", "pct": 70}
                         ]
                     }]
                 }]
             }]
         }),
         Fire({
             'activity':[{
                 "active_areas":[{
                     "specified_points": [{
                         "area": 5,
                         "lat": 44,
                         "lng": -117,
                         "fuelbeds":[
                             {"fccs_id": "2", "pct": 10},
                             {"fccs_id": "3", "pct": 90}
                         ]
                     }]
                 }]
             }]
         })
     ]
     expected_summary = [
         {"fccs_id": "1", "pct": 20},
         {"fccs_id": "2", "pct": 50},
         {"fccs_id": "3", "pct": 30}
     ]
     summary = fuelbeds.summarize(fires)
     assert summary == expected_summary
Пример #7
0
    def load(self):
        data = self._load()
        self._save_copy(data)

        fires = []
        for fire in self._marshal(data):
            with skip_failures(self._skip_failures):
                # cast each fire to Fire object, in case child class
                #   did override _marshal but didn't return Fire objects?
                fire = Fire(fire)
                self._prune_activity(fire)
                if fire['activity']:
                    fires.append(fire)

        return fires
Пример #8
0
 def test_merge_three(self, monkeypatch):
     monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')
     expected = Fire({
         "id": "1234abcd",
         "original_fire_ids": {"bbb", "ddd", "eee"},
         "meta": {'foo': 'bar', 'bar': 'CONFLICT'},
         "start": datetime.datetime(2015,8,4,17,0,0),
         "end": datetime.datetime(2015,8,4,23,0,0),
         # TODO: fill in ....
     })
     actual = self.merger._merge_fires([
         copy.deepcopy(self.FIRE_1),
         copy.deepcopy(self.FIRE_2),
         copy.deepcopy(self.FIRE_3)
     ])
Пример #9
0
    def test_merge_two(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')
        expected = Fire({
            "id": "1234abcd",
            "original_fire_ids": ["bbb", "ddd", "eee"],
            "meta": {'foo': 'bar', 'bar': 'baz'},
            "start": datetime.datetime(2015,8,4,17,0,0),
            "end": datetime.datetime(2015,8,4,19,0,0),
            "area": 270.0, "latitude": 47.5, "longitude": -121.6, "utc_offset": -7.0,
            "plumerise": {
                "2015-08-04T17:00:00": {
                    # "emission_fractions": [0.4, 0.2, 0.2, 0.2],
                    # "emission_fractions": [0.1, 0.3, 0.4,0.2],
                    "emission_fractions": [0.34, 0.22, 0.4, 0.04],
                    "heights": [90.0, 192.5, 295.0, 397.5, 500.0],
                    # need to write out how the result is calculated so that
                    # we have the same rounding error in expected as we have
                    # in actual
                    "smolder_fraction": (0.05*10 + 0.06*2.5) / 12.5  # == 0.052
                },
                "2015-08-04T18:00:00": {
                    "emission_fractions": [0.5, 0.2, 0.2, 0.1],
                    "heights": [300, 350, 400, 425, 450],
                    "smolder_fraction": 0.05
                }
            },
            "timeprofiled_area": {
                "2015-08-04T17:00:00": 32.0,
                "2015-08-04T18:00:00": 30.0

            },
            "timeprofiled_emissions": {
                "2015-08-04T17:00:00": {"CO": 23.0, "PM2.5": 12.5},
                "2015-08-04T18:00:00": {"CO": 3.0, "PM2.5": 5.0}
            },
            "consumption": {
                "flaming": 1511,
                "residual": 1549,
                "smoldering": 1317,
                "total": 4427
            },
            "heat": 3000000.0
        })
        actual = self.merger._merge_fires([
            copy.deepcopy(self.FIRE_1),
            copy.deepcopy(self.FIRE_2)
        ])
        assert actual == expected
Пример #10
0
    def _marshal(self, data):
        # TODO: support config setting 'skip_failures'
        # v2 didn't initially have version specified in the output data
        if not data.get('version') or data['version'].startswith("2."):
            func = Blueskyv4_0To4_1().marshal

        elif data['version'].startswith("3."):
            # Nothing needs be done; just return fires
            func = lambda fires: [Fire(f) for f in fires]

        else:
            raise NotImplementedError(
                "Support for FireSpider "
                "version %s not implemented", data['version'])

        return func(data.get('data', []))
Пример #11
0
    def _add_location(self, fire, aa, loc, activity_fields, utc_offset,
                      loc_num):
        if any([not loc.get(f) for f in activity_fields]):
            raise ValueError("Each active area must have {} in "
                             "order to compute {} dispersion".format(
                                 ','.join(activity_fields),
                                 self.__class__.__name__))
        if any([not fb.get('emissions') for fb in loc['fuelbeds']]):
            raise ValueError(
                "Missing emissions data required for computing dispersion")

        heat = self._get_heat(fire, loc)
        plumerise, timeprofile = self._get_plumerise_and_timeprofile(
            loc, utc_offset)
        emissions = self._get_emissions(loc)
        timeprofiled_emissions = self._get_timeprofiled_emissions(
            timeprofile, emissions)
        timeprofiled_area = {
            dt: e.get('area_fraction') * loc['area']
            for dt, e in timeprofile.items()
        }

        # consumption = datautils.sum_nested_data(
        #     [fb.get("consumption", {}) for fb in a['fuelbeds']], 'summary', 'total')
        consumption = loc['consumption']['summary']

        latlng = locationutils.LatLng(loc)

        f = Fire(
            id="{}-{}".format(fire.id, loc_num),
            # See note above, in _merge_two_fires, about why
            # original_fire_ids is a set instead of scalar
            original_fire_ids=set([fire.id]),
            meta=fire.get('meta', {}),
            start=aa['start'],
            end=aa['end'],
            area=loc['area'],
            latitude=latlng.latitude,
            longitude=latlng.longitude,
            utc_offset=utc_offset,
            plumerise=plumerise,
            timeprofiled_emissions=timeprofiled_emissions,
            timeprofiled_area=timeprofiled_area,
            consumption=consumption)
        if heat:
            f['heat'] = heat
        self._fires.append(f)
Пример #12
0
    def test_contiguous_time_windows(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')

        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_fire_contiguous_time_windows = copy.deepcopy(self.FIRE_CONTIGUOUS_TIME_WINDOWS)

        # *should* be merged
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1, self.FIRE_CONTIGUOUS_TIME_WINDOWS])

        expected_merged_fires = [
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'asdasd'},
                "start": datetime.datetime(2015,8,4,17,0,0),
                "end": datetime.datetime(2015,8,4,21,0,0),
                "area": 220.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T17:00:00": PLUMERISE_HOUR,
                    "2015-08-04T18:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T19:00:00": PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T17:00:00": 12.0,
                    "2015-08-04T18:00:00": 0.0,
                    "2015-08-04T19:00:00": 10.0,
                    "2015-08-04T20:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T17:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T18:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T19:00:00": {"CO": 0.0, "PM2.5": 5.0},  # == 10.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T20:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 4000000.0
            })
        ]

        assert len(merged_fires) == len(expected_merged_fires)
        assert merged_fires == expected_merged_fires

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_CONTIGUOUS_TIME_WINDOWS == original_fire_contiguous_time_windows
Пример #13
0
 def _merge_two_fires(self, f_merged, f):
     new_f_merged = Fire(
         # We'll let the new fire be assigned a new id
         # It's possible, but not likely, that locations from different
         # fires will get merged together.  This set of original fire
         # ids isn't currently used other than in log messages, but
         # could be used in tranching
         original_fire_ids=f_merged.original_fire_ids.union(
             f.original_fire_ids),
         # we know at this point that their meta dicts don't conflict
         meta=dict(f_merged.meta, **f.meta),
         # there may be a gap between f_merged['end'] and f['start']
         # but no subsequent fires will be in that gap, since
         # fires were sorted by 'start'
         # Note: we need to use f_merged['start'] instead of f_merged.start
         #   because the Fire model has special property 'start' that
         #   returns the first start time of all active_areas in the fire's
         #   activity, and since we're not using nested activity here,
         #   f_merged.start returns 'None' rather than the actual value
         #   set in _add_location
         start=f_merged['start'],
         # end will only be used when merging fires
         # Note: see note about 'start', above
         end=f['end'],
         area=f_merged.area + f.area,
         # f_merged and f have the same lat,lng (o.w. they wouldn't
         # be merged)
         latitude=f_merged.latitude,
         longitude=f_merged.longitude,
         # the offsets could be different, but only if on DST transition
         # TODO: Should we worry about this?  If so, we should add same
         #   utc offset to criteria for deciding to merge or not
         utc_offset=f_merged.utc_offset,
         plumerise=self._merge_hourly_data(f_merged.plumerise, f.plumerise,
                                           f['start']),
         timeprofiled_area=self._merge_hourly_data(
             f_merged.timeprofiled_area, f.timeprofiled_area, f['start']),
         timeprofiled_emissions=self._merge_hourly_data(
             f_merged.timeprofiled_emissions, f.timeprofiled_emissions,
             f['start']),
         consumption=self._sum_data(f_merged.consumption, f.consumption))
     if 'heat' in f_merged or 'heat' in f:
         new_f_merged['heat'] = f_merged.get('heat', 0.0) + f.get(
             'heat', 0.0)
     return new_f_merged
Пример #14
0
 def test_one_fire(self):
     fires = [
         Fire({
             'activity': [{
                 "location": {
                     "area": 10
                 },
                 "fuelbeds": [{
                     "fccs_id": "1",
                     "pct": 40
                 }, {
                     "fccs_id": "2",
                     "pct": 60
                 }]
             }]
         })
     ]
     summary = fuelbeds.summarize(fires)
     assert summary == fires[0]['activity'][0]['fuelbeds']
Пример #15
0
    def _merge_fires(self, fires):
        if len(fires) == 1:
            return fires[0]

        logging.debug("Merging plumes of %s fires in for hysplit", len(fires))

        latLng = self._get_centroid(fires)

        new_f_merged = Fire(
            # We'll let the new fire be assigned a new id
            # It's possible, but not likely, that locations from different
            # fires will get merged together.  This set of original fire
            # ids isn't currently used other than in log messages, but
            # could be used in tranching
            original_fire_ids=sorted(
                list(set.union(*[f.original_fire_ids for f in fires]))),
            meta=self._merge_meta(fires),
            # Note: we need to use f['start'] instead of f.start
            #   because the Fire model has special property 'start' that
            #   returns the first start time of all active_areas in the fire's
            #   activity, and since we're not using nested activity here,
            #   f.start returns 'None' rather than the actual value
            #   set in _add_location
            start=min([f['start'] for f in fires]),
            # end will only be used when merging fires
            # Note: see note about 'start', above
            end=max([f['end'] for f in fires]),
            area=sum([f.area for f in fires]),
            latitude=latLng.latitude,
            longitude=latLng.longitude,
            # the offsets could be different, but just take first
            # TODO: look up utc offset of centroid potision
            utc_offset=fires[0].utc_offset,
            plumerise=self._merge_plumerise(fires),
            timeprofiled_area=self._sum_data(fires, 'timeprofiled_area'),
            timeprofiled_emissions=self._sum_data(fires,
                                                  'timeprofiled_emissions'),
            consumption=self._sum_data(fires, 'consumption'))
        heat_values = [f['heat'] for f in fires if f.get('heat') is not None]
        if heat_values:
            new_f_merged['heat'] = sum(heat_values)
        return new_f_merged
Пример #16
0
 def test_one_fire(self):
     fires = [
         Fire({
             'activity':[{
                 "active_areas":[{
                     "specified_points": [{
                         "area": 10,
                         "lat": 45,
                         "lng": -118,
                         "fuelbeds":[
                             {"fccs_id": "1", "pct": 40},
                             {"fccs_id": "2", "pct": 60}
                         ]
                     }]
                 }]
             }]
         })
     ]
     expected_summary = [
         {"fccs_id": "1", "pct": 40},
         {"fccs_id": "2", "pct": 60}
     ]
     summary = fuelbeds.summarize(fires)
     assert summary == expected_summary
Пример #17
0
class TestPlumeMerger_AggregatePlumeriseHour(BaseTestPlumeMerger):

    FIRE_1 = Fire({
        "plumerise": {
            "2015-08-04T17:00:00": {
                "emission_fractions": [0.4, 0.2, 0.2, 0.2],
                "heights": [90, 250, 300, 325, 350],
                "smolder_fraction": 0.05
            }
        },
        "timeprofiled_emissions": {
            "2015-08-04T17:00:00": {"CO": 22.0, "PM2.5": 10.0}
        }
    })

    FIRE_2 = Fire({
        "plumerise": {
            "2015-08-04T17:00:00": {
                "emission_fractions": [0.1,0.3,0.4,0.2],
                "heights": [100,200,300,400,500],
                "smolder_fraction": 0.06
            },
            "2015-08-04T18:00:00": {
                "emission_fractions": [0.5, 0.2, 0.2, 0.1],
                "heights": [300, 350, 400, 425, 450],
                "smolder_fraction": 0.05
            }
        },
        "timeprofiled_emissions": {
            "2015-08-04T17:00:00": {"CO": 1.0, "PM2.5": 2.5},
            "2015-08-04T18:00:00": {"CO": 3.0, "PM2.5": 5.0},
        }
    })

    # Note: no need to test one fire, since _aggregate_plumerise_hour
    #   will never get called with one fire

    def test_two_fires_no_merge(self):
        expected = (
            [(325, 2.5), (375, 1.0), (412.5, 1.0), (437.5, 0.5)],
            300, 450, 0.25, 5.0
        )
        fires = [
            copy.deepcopy(self.FIRE_1),
            copy.deepcopy(self.FIRE_2)
        ]
        actual = self.merger._aggregate_plumerise_hour(
            fires, "2015-08-04T18:00:00")
        assert expected == actual

    def test_two_fires_w_merge(self):
        expected = (

            [(150, 0.25), (170, 4), (250, 0.75), (275, 2),
            (312.5, 2), (337.5, 2), (350, 1.0), (450, 0.5)],
            90, 500, 0.5+0.15, 12.5
        )

        fires = [
            copy.deepcopy(self.FIRE_1),
            copy.deepcopy(self.FIRE_2)
        ]
        assert expected == self.merger._aggregate_plumerise_hour(
            fires, "2015-08-04T17:00:00")
Пример #18
0
    def _process_row(self, row):
        fire = Fire({
            "id":
            row.get("id") or str(uuid.uuid4()),
            "event_of": {},
            # Though unlikely, it's possible that the points in
            # a single fire span multiple time zones
            "specified_points_by_date_n_offset":
            defaultdict(lambda: defaultdict(lambda: []))
        })

        start, utc_offset = self._parse_date_time(row["date_time"])
        if not start:
            raise ValueError("Fire location missing time information")

        sp = {
            loc_attr: f(row.get(csv_key))
            for csv_key, loc_attr, f in LOCATION_FIELDS
        }
        if self._omit_nulls:
            sp = {k: v for k, v in sp.items() if v is not None}

        fire['specified_points_by_date_n_offset'][start][utc_offset].append(sp)

        # event and type could have been set when the Fire object was
        # instantiated, but checking amd setting here allow the fields to
        # be set even if only subsequent rows for the fire have them defined
        if row.get("event_id"):
            fire["event_of"]["id"] = row["event_id"]
        if row.get("event_url"):
            fire["event_of"]["url"] = row["event_url"]

        if row.get("type"):
            fire["type"] = row["type"].lower()

        # Add consumption data if present and flag active.
        # This was implemented for the Canadian version of the SmartFire system.
        # It is important to note that this consumption marshalling was done with only the Canadian format
        # in mind. If consumption is added to the input of the US system, further changes maybe required.
        if self._load_consumption:
            flaming = 0
            smold = 0
            resid = 0
            duff = 0

            if row.get("consumption_flaming") is not None:
                flaming = get_optional_float(row.get("consumption_flaming"))
            if row.get("consumption_smoldering") is not None:
                smold = get_optional_float(row.get("consumption_smoldering"))
            if row.get("consumption_residual") is not None:
                resid = get_optional_float(row.get("consumption_residual"))
            if row.get("consumption_duff") is not None:
                duff = get_optional_float(row.get("consumption_duff"))

            total_cons = flaming + smold + resid + duff

            area = sp.get('area') or 1
            consumption = {
                "flaming": [area * flaming],
                "residual": [area * resid],
                "smoldering": [area * smold],
                "duff": [area * duff],
                "total": [area * total_cons]
            }

            self._consumption_values[fire["id"]] = consumption

        # TODO: other marshaling

        return fire
Пример #19
0
    Fire({
        'source':
        'GOES-16',
        'type':
        "wf",
        "activity": [{
            "active_areas": [{
                "start":
                "2018-06-27T00:00:00",
                "end":
                "2018-06-28T00:00:00",
                "ignition_start":
                "2018-06-27T09:00:00",
                "ignition_end":
                "2018-06-28T11:00:00",
                "utc_offset":
                "-04:00",
                'slope':
                5,
                'windspeed':
                5,
                'rain_days':
                10,
                'moisture_10hr':
                50,
                'fm_type':
                "MEAS-Th",
                'moisture_1khr':
                50,
                'moisture_duff':
                50,
                'moisture_litter':
                30,
                'canopy_consumption_pct':
                0,
                'shrub_blackened_pct':
                50,
                'pile_blackened_pct':
                0,
                "specified_points": [{
                    'area': 47.20000000000001,
                    'lat': 50.632,
                    'lng': -71.362,

                    # timeprofile should be ignored and replcaced when running
                    # FRP emissions
                    "timeprofile": {
                        "2018-06-27T17:00:00": {
                            'area_fraction': 0.75,
                            'flaming': 0.75,
                            'smoldering': 0.3,
                            'residual': 0.0
                        },
                        "2018-06-27T20:00:00": {
                            'area_fraction': 0.25,
                            'flaming': 0.25,
                            'smoldering': 0.7,
                            'residual': 1.0
                        }
                    },
                    # Hourly FRP is used for FRP emissions
                    "hourly_frp": {
                        "2018-06-27T10:00:00": 55.4,
                        "2018-06-27T11:00:00": 66,
                        "2018-06-27T12:00:00": 78,
                        "2018-06-27T13:00:00": 83,
                        "2018-06-27T18:00:00": 82,
                        "2018-06-27T19:00:00": 66,
                        "2018-06-27T20:00:00": 52.5
                    },
                    #55.4 + 66 + 78 + 83 + 82 + 66 + 52.5 = 482.9
                    "frp": 482.9,
                }]
            }]
        }]
    }),
Пример #20
0
    def marshal_fire(self, fire):
        # TODO: break this method up into multiple methods
        fire = copy.deepcopy(fire)

        activity = fire.pop('activity', None) or fire.pop('growth', [])
        fire['activity'] = []
        for old_a in activity:
            loc = old_a.pop('location', {})
            lat = loc.pop('latitude', None)
            lng = loc.pop('longitude', None)
            area = loc.pop('area', None)
            geojson = loc.pop('geojson', None)

            aa_template = dict(loc)
            new_a = {'active_areas': []}

            loc_template = {}
            for k in EXTRA_LOC_FIELDS:
                val = old_a.pop(k, None)
                if val:
                    loc_template[k] = val

            for k in TO_COPY_UP:
                val = old_a.pop(k, None)
                if val:
                    loc_template[k] = val
                    new_a[k] = val
                    aa_template[k] = val

            aa_template.update(**old_a)

            if lat is not None and lng is not None:
                aa = copy.deepcopy(aa_template)
                aa["specified_points"] = [
                    dict(loc_template, lat=lat, lng=lng, area=area)
                ]
                new_a["active_areas"].append(aa)

            elif geojson:
                if geojson['type'] == 'Polygon':
                    aa = copy.deepcopy(aa_template)
                    aa["perimeter"] = dict(loc_template,
                        polygon=geojson['coordinates'][0], area=area)
                    new_a["active_areas"].append(aa)

                elif geojson['type'] == 'MultiPolygon':
                    for p in geojson['coordinates']:
                        aa = copy.deepcopy(aa_template)
                        aa["perimeter"] = dict(loc_template,
                            polygon=p[0], area=area)
                        new_a['active_areas'].append(aa)

                elif geojson['type'] == 'MultiPoint' and area:
                    aa = copy.deepcopy(aa_template)
                    num_points = len(geojson['coordinates'])
                    aa["specified_points"] = [
                        dict(loc_template,lat=p[1],lng=p[0],
                            area=area / num_points)
                        for p in geojson['coordinates']
                    ]
                    new_a['active_areas'].append(aa)

                elif geojson['type'] == 'Point' and area:
                    aa = copy.deepcopy(aa_template)
                    aa["specified_points"] = [
                        dict(loc_template,lat=geojson['coordinates'][1],
                            lng=geojson['coordinates'][0], area=area)
                    ]
                    new_a['active_areas'].append(aa)

                else:
                    raise ValueError("Can't convert fire: %s", fire)

            else:
                raise ValueError("Can't convert fire: %s", fire)

            fire['activity'].append(new_a)

        return Fire(fire)
Пример #21
0
 def __init__(self, fires):
     self.fires = [Fire(f) for f in fires]
Пример #22
0
    def test_one_fire_no_event(self):
        fire = Fire({
            "fuel_type":
            "natural",
            "id":
            "SF11C14225236095807750",
            "type":
            "wildfire",
            "activity": [{
                "active_areas": [{
                    "start":
                    "2015-08-04T18:00:00",
                    "end":
                    "2015-08-05T18:00:00",
                    "utc_offset":
                    "-06:00",
                    "specified_points": [{
                        "lat": 35.0,
                        "lng": -96.2,
                        "area": 99
                    }]
                }, {
                    "start":
                    "2015-08-04T17:00:00",
                    "end":
                    "2015-08-05T17:00:00",
                    "utc_offset":
                    "-07:00",
                    "canopy_consumption_pct":
                    23.3,
                    "min_wind":
                    34,
                    "specified_points": [{
                        "lat": 30.0,
                        "lng": -116.2,
                        "area": 102
                    }, {
                        "area":
                        120.0,
                        "rain_days":
                        8,
                        "slope":
                        20.0,
                        "snow_month":
                        5,
                        "sunrise_hour":
                        4,
                        "sunset_hour":
                        19,
                        "consumption": {
                            "summary": {
                                "flaming": 1311.2071801109494,
                                "residual": 1449.3962581338644,
                                "smoldering": 1267.0712004277434,
                                "total": 4027.6746386725567
                            }
                        },
                        "fuelbeds": [{
                            "emissions": {
                                "flaming": {
                                    "PM2.5": [9.545588271207714]
                                },
                                "residual": {
                                    "PM2.5": [24.10635856528243]
                                },
                                "smoldering": {
                                    "PM2.5": [21.073928205514225]
                                },
                                "total": {
                                    "PM2.5": [54.725875042004375]
                                }
                            },
                            "fccs_id": "9",
                            "heat": {
                                "flaming": [20979314881.77519],
                                "residual": [23190340130.141827],
                                "smoldering": [20273139206.843895],
                                "total": [64442794218.7609]
                            },
                            "pct": 100.0
                        }],
                        "lat":
                        47.41,
                        "lng":
                        -121.41
                    }],
                    "state":
                    "WA"
                }],
            }]
        })

        writer = firescsvs.FiresCsvsWriter('/foo')
        fires_fields, events_fields = writer._collect_csv_fields([fire])

        expected_fires_fields = [{
            "area": 99,
            "canopy_consumption_pct": '',
            'ch4': '',
            'co': '',
            'co2': '',
            'consumption_flaming': '',
            'consumption_residual': '',
            'consumption_smoldering': '',
            'consumption_total': '',
            'country': '',
            'county': '',
            'date_time': '20150804',
            'elevation': '',
            'event_id': '',
            'event_name': '',
            'fccs_number': '',
            'heat': '',
            'id': 'SF11C14225236095807750',
            "latitude": 35.0,
            "longitude": -96.2,
            'max_humid': '',
            'max_temp': '',
            'max_temp_hour': '',
            'max_wind': '',
            'max_wind_aloft': '',
            'min_humid': '',
            'min_temp': '',
            'min_temp_hour': '',
            'min_wind': '',
            'min_wind_aloft': '',
            'moisture_100hr': '',
            'moisture_10hr': '',
            'moisture_1hr': '',
            'moisture_1khr': '',
            'moisture_duff': '',
            'moisture_live': '',
            'nh3': '',
            'nox': '',
            'pm10': '',
            'pm2.5': '',
            'rain_days': '',
            'slope': '',
            'snow_month': '',
            'so2': '',
            'state': '',
            'sunrise_hour': '',
            'sunset_hour': '',
            'type': 'WF',
            "utc_offset": "-06:00",
            'voc': ''
        }, {
            "area": 102,
            "canopy_consumption_pct": 23.3,
            'ch4': '',
            'co': '',
            'co2': '',
            'consumption_flaming': '',
            'consumption_residual': '',
            'consumption_smoldering': '',
            'consumption_total': '',
            'country': '',
            'county': '',
            'date_time': '20150804',
            'elevation': '',
            'event_id': '',
            'event_name': '',
            'fccs_number': '',
            'heat': '',
            'id': 'SF11C14225236095807750',
            "latitude": 30.0,
            "longitude": -116.2,
            'max_humid': '',
            'max_temp': '',
            'max_temp_hour': '',
            'max_wind': '',
            'max_wind_aloft': '',
            'min_humid': '',
            'min_temp': '',
            'min_temp_hour': '',
            "min_wind": 34,
            'min_wind_aloft': '',
            'moisture_100hr': '',
            'moisture_10hr': '',
            'moisture_1hr': '',
            'moisture_1khr': '',
            'moisture_duff': '',
            'moisture_live': '',
            'nh3': '',
            'nox': '',
            'pm10': '',
            'pm2.5': '',
            'rain_days': '',
            'slope': '',
            'snow_month': '',
            'so2': '',
            'state': 'WA',
            'sunrise_hour': '',
            'sunset_hour': '',
            'type': 'WF',
            "utc_offset": "-07:00",
            'voc': ''
        }, {
            'area': 120.0,
            "canopy_consumption_pct": 23.3,
            'ch4': '',
            'co': '',
            'co2': '',
            'consumption_flaming': 1311.2071801109494,
            'consumption_residual': 1449.3962581338644,
            'consumption_smoldering': 1267.0712004277434,
            'consumption_total': 4027.6746386725567,
            'country': '',
            'county': '',
            'date_time': '20150804',
            'elevation': '',
            'event_id': '',
            'event_name': '',
            'fccs_number': '9',
            'heat': 64442794218.7609,
            'id': 'SF11C14225236095807750',
            'latitude': 47.41,
            'longitude': -121.41,
            'max_humid': '',
            'max_temp': '',
            'max_temp_hour': '',
            'max_wind': '',
            'max_wind_aloft': '',
            'min_humid': '',
            'min_temp': '',
            'min_temp_hour': '',
            'min_wind': 34,
            'min_wind_aloft': '',
            'moisture_100hr': '',
            'moisture_10hr': '',
            'moisture_1hr': '',
            'moisture_1khr': '',
            'moisture_duff': '',
            'moisture_live': '',
            'nh3': '',
            'nox': '',
            'pm10': '',
            'pm2.5': 54.725875042004375,
            'rain_days': 8.0,
            'slope': 20.0,
            'snow_month': 5.0,
            'so2': '',
            'state': 'WA',
            'sunrise_hour': 4.0,
            'sunset_hour': 19.0,
            'type': 'WF',
            'utc_offset': '-07:00',
            'voc': ''
        }]

        assert len(fires_fields) == len(expected_fires_fields)
        for i in range(len(fires_fields)):
            assert fires_fields[i].keys() == expected_fires_fields[i].keys()
            for k in fires_fields[i]:
                assert fires_fields[i][k] == expected_fires_fields[i][
                    k], "{} differs".format(k)

        expected_events_fields = {}
        assert events_fields == expected_events_fields
Пример #23
0
"""Unit tests for bluesky.modules.findmetdata"""

__author__ = "Joel Dubowy"

import copy
import datetime
from py.test import raises

from bluesky.config import Config
from bluesky.exceptions import BlueSkyConfigurationError
from bluesky.models.fires import FiresManager, Fire
from bluesky.modules import findmetdata

FIRE_NO_ACTIVITY = Fire({"id": "SF11C14225236095807750"})

FIRE_1 = Fire({
    "activity": [{
        "active_areas": [
            {
                "start": "2015-01-20T17:00:00",
                "end": "2015-01-21T17:00:00",
                "ecoregion": "southern",
                "utc_offset": "-09:00",
                "specified_points": [{
                    "lat": 45,
                    "lng": -119,
                }]
            },
            {
                "pct": 40,
                "start": "2015-01-20T17:00:00",  # SAME TIME WINDOW
Пример #24
0
"""Unit tests for bluesky.modules.findmetdata"""

__author__ = "Joel Dubowy"

import datetime

from py.test import raises
from met.arl import arlprofiler

from bluesky.config import defaults
from bluesky.exceptions import BlueSkyConfigurationError
from bluesky.models.fires import FiresManager, Fire
from bluesky.modules import localmet

FIRE_NO_ACTIVITY = Fire({"id": "SF11C14225236095807750"})

FIRE = Fire({
    "activity": [{
        "active_areas": [
            {
                "start": "2015-01-20T17:00:00",
                "end": "2015-01-21T17:00:00",
                "ecoregion": "southern",
                "utc_offset": "-07:00",
                "specified_points": [{
                    "lat": 45,
                    "lng": -119,
                    "area": 123
                }]
            },
            {
Пример #25
0
    def test_all(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')

        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_overlapping_time_windows = copy.deepcopy(self.FIRE_OVERLAPPING_TIME_WINDOWS)
        original_fire_contiguous_time_windows = copy.deepcopy(self.FIRE_CONTIGUOUS_TIME_WINDOWS)
        original_fire_non_contiguous_time_windows = copy.deepcopy(self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS)
        original_fire_conflicting_meta = copy.deepcopy(self.FIRE_CONFLICTING_META)
        original_fire_different_lat_lng = copy.deepcopy(self.FIRE_DIFFERENT_LAT_LNG)

        merged_fires = firemerge.FireMerger().merge([
            self.FIRE_1,
            self.FIRE_OVERLAPPING_TIME_WINDOWS,
            self.FIRE_CONTIGUOUS_TIME_WINDOWS,
            self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS,
            self.FIRE_CONFLICTING_META,
            self.FIRE_DIFFERENT_LAT_LNG
        ])

        expected_merged_fires = [
            # FIRE_1 merged with FIRE_CONTIGUOUS_TIME_WINDOWS
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'asdasd'},
                "start": datetime.datetime(2015,8,4,17,0,0),
                "end": datetime.datetime(2015,8,4,21,0,0),
                "area": 220.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T17:00:00": PLUMERISE_HOUR,
                    "2015-08-04T18:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T19:00:00": PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T17:00:00": 12.0,
                    "2015-08-04T18:00:00": 0.0,
                    "2015-08-04T19:00:00": 10.0,
                    "2015-08-04T20:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T17:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T18:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T19:00:00": {"CO": 0.0, "PM2.5": 5.0},  # == 10.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T20:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 4000000.0
            }),
            # FIRE_OVERLAPPING_TIME_WINDOWS merged with
            # FIRE_NON_CONTIGUOUS_TIME_WINDOWS
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'sdf'},
                "start": datetime.datetime(2015,8,4,18,0,0),
                "end": datetime.datetime(2015,8,4,22,0,0),
                "area": 240.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T18:00:00": PLUMERISE_HOUR,
                    "2015-08-04T19:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": PLUMERISE_HOUR,
                    "2015-08-04T21:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T18:00:00": 12.0,
                    "2015-08-04T19:00:00": 0.0,
                    "2015-08-04T20:00:00": 12.0,
                    "2015-08-04T21:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T18:00:00": {"CO": 0.0, "PM2.5": 4.0},
                    "2015-08-04T19:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T20:00:00": {"CO": 0.0, "PM2.5": 4.0},
                    "2015-08-04T21:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 6000000.0
            }),
            self.FIRE_CONFLICTING_META,
            self.FIRE_DIFFERENT_LAT_LNG
        ]

        assert len(merged_fires) == len(expected_merged_fires)
        assert merged_fires == expected_merged_fires

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_OVERLAPPING_TIME_WINDOWS == original_overlapping_time_windows
        assert self.FIRE_CONTIGUOUS_TIME_WINDOWS == original_fire_contiguous_time_windows
        assert self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS == original_fire_non_contiguous_time_windows
        assert self.FIRE_CONFLICTING_META == original_fire_conflicting_meta
        assert self.FIRE_DIFFERENT_LAT_LNG == original_fire_different_lat_lng
Пример #26
0
fire = Fire({
    'type': "rx",
    "activity":  [
        {
            "active_areas": [
                {
                    "start": "2018-06-27T00:00:00",
                    "end": "2018-06-28T00:00:00",
                    "ignition_start": "2018-06-27T09:00:00",
                    "ignition_end": "2018-06-28T11:00:00",
                    "utc_offset": "-07:00",
                    "ecoregion": "western",
                    'slope': 5,
                    'windspeed': 5,
                    'rain_days': 10,
                    'moisture_10hr': 50,
                    'fm_type':  "MEAS-Th",
                    'moisture_1khr': 50,
                    'moisture_duff': 50,
                    'moisture_litter': 30,
                    'canopy_consumption_pct':  0,
                    'shrub_blackened_pct':  50,
                    'pile_blackened_pct':  0,
                    "specified_points": [
                        {
                            'area': 50.4,
                            'lat': 45.632,
                            'lng': -120.362,
                            "fuelbeds": [
                                {
                                    "fccs_id": "52",
                                    "pct": 100.0
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
})
Пример #27
0
    def test_bucket_fires(self):
        fires = [
            # The following two fires will be in their own buckets, even
            # though they're within what would be a grid cell, because they're
            # outside the merging boundary
            Fire({'id': '1', 'latitude': 32.1, 'longitude': -100.1}),
            Fire({'id': '1.5', 'latitude': 32.2, 'longitude': -100.2}),
            Fire({'id': '2', 'latitude': 36.22, 'longitude': -111.1}),
            Fire({'id': '3', 'latitude': 32.2, 'longitude': -110}),
            Fire({'id': '4', 'latitude': 32.2, 'longitude': -111.2}),
            Fire({'id': '5', 'latitude': 36.1, 'longitude': -111.4}),
            Fire({'id': '6', 'latitude': 36.4, 'longitude': -111.3})
        ]
        expected = [
            [
                Fire({'id': '5', 'latitude': 36.1, 'longitude': -111.4}),
                Fire({'id': '6', 'latitude': 36.4, 'longitude': -111.3}),
                Fire({'id': '2', 'latitude': 36.22, 'longitude': -111.1})
            ],
            [Fire({'id': '4', 'latitude': 32.2, 'longitude': -111.2})],
            [Fire({'id': '3', 'latitude': 32.2, 'longitude': -110})],
            [Fire({'id': '1.5', 'latitude': 32.2, 'longitude': -100.2})],
            [Fire({'id': '1', 'latitude': 32.1, 'longitude': -100.1})]
        ]
        actual = self.merger._bucket_fires(fires)
        # sort to compare
        for a in actual:
            a.sort(key=lambda f: f.longitude)
        actual.sort(key=lambda fl: fl[0].longitude)

        assert actual == expected
Пример #28
0
import datetime
import os
import tempfile
import uuid

from py.test import raises
from plumerise import sev, feps
from pyairfire import osutils

from bluesky.config import Config, defaults
from bluesky.exceptions import BlueSkyConfigurationError
from bluesky.models.fires import FiresManager, Fire
from bluesky.models import activity
from bluesky.modules import plumerise

FIRE_NO_ACTIVITY = Fire({"id": "SF11C14225236095807750"})

FIRE_MISSING_LOCATION_AREA = Fire({
    "activity": [{
        "active_areas": [{
            "start": "2015-01-20T17:00:00",
            "end": "2015-01-21T17:00:00",
            "ecoregion": "southern",
            "utc_offset": "-07:00",
            "specified_points": [{
                "lat": 45,
                "lng": -119
            }]
        }]
    }]
})
Пример #29
0
class TestPlumeMerger_MergeFires(BaseTestPlumeMerger):

    FIRE_1 =  Fire({
        "id": "aaa",
        "original_fire_ids": {"bbb"},
        "meta": {'foo': 'bar'},
        "start": datetime.datetime(2015,8,4,17,0,0),
        "end": datetime.datetime(2015,8,4,18,0,0),
        "area": 120.0, "latitude": 47.4, "longitude": -121.5, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T17:00:00": {
                "emission_fractions": [0.4, 0.2, 0.2, 0.2],
                "heights": [90, 250, 300, 325, 350],
                "smolder_fraction": 0.05
            }
        },
        "timeprofiled_area": {
            "2015-08-04T17:00:00": 12.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T17:00:00": {"CO": 22.0, "PM2.5": 10.0}
        },
        "consumption": {
            "flaming": 1311,
            "residual": 1449,
            "smoldering": 1267,
            "total": 4027
        },
        "heat": 1000000.0
    })

    FIRE_2 = Fire({
        "id": "ccc",
        "original_fire_ids": {"ddd", "eee"},
        "meta": {'foo': 'bar', 'bar': 'baz'},
        "start": datetime.datetime(2015,8,4,17,0,0),
        "end": datetime.datetime(2015,8,4,19,0,0),
        "area": 150.0, "latitude": 47.6, "longitude": -121.7, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T17:00:00": {
                "emission_fractions": [0.1,0.3,0.4,0.2],
                "heights": [100,200,300,400,500],
                "smolder_fraction": 0.06
            },
            "2015-08-04T18:00:00": {
                "emission_fractions": [0.5, 0.2, 0.2, 0.1],
                "heights": [300, 350, 400, 425, 450],
                "smolder_fraction": 0.05
            }
        },
        "timeprofiled_area": {
            "2015-08-04T17:00:00": 20.0,
            "2015-08-04T18:00:00": 30.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T17:00:00": {"CO": 1.0, "PM2.5": 2.5},
            "2015-08-04T18:00:00": {"CO": 3.0, "PM2.5": 5.0}
        },
        "consumption": {
            "flaming": 200,
            "residual": 100,
            "smoldering": 50,
            "total": 400
        },
        "heat": 2000000.0
    })

    FIRE_3 = Fire({
        "id": "ccc",
        "original_fire_ids": {"ddd", "eee"},
        "meta": {'foo': 'bar', 'bar': 'CONFLICT'},
        "start": datetime.datetime(2015,8,4,22,0,0),
        "end": datetime.datetime(2015,8,4,23,0,0),
        "area": 150.0, "latitude": 47.6, "longitude": -121.7, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T22:00:00": {
                "emission_fractions": [0.2, 0.2, 0.2, 0.4],
                "heights": [111, 222, 333, 444, 555],
                "smolder_fraction": 0.05
            }
        },
        "timeprofiled_area": {
            "2015-08-04T22:00:00": 20.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T22:00:00": {"CO": 1.0, "PM2.5": 4.0}
        },
        "consumption": {
            "flaming": 100,
            "residual": 2,
            "smoldering": 33,
            "total": 135
        },
        "heat": 2000.0
    })

    def setup(self):
        # The config won't come into play in these tests, since we're
        # calling _merge_fires directly
        self.merger = firemerge.PlumeMerger({
            "grid": {
                "spacing": 0.5,
                "boundary": {
                  "sw": { "lat": 30, "lng": -110 },
                  "ne": { "lat": 40, "lng": -120 }
                }
            }
        })

    def test_merge_one(self):
        assert self.FIRE_1 == self.merger._merge_fires([
            copy.deepcopy(self.FIRE_1)
        ])

    def test_merge_two(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')
        expected = Fire({
            "id": "1234abcd",
            "original_fire_ids": ["bbb", "ddd", "eee"],
            "meta": {'foo': 'bar', 'bar': 'baz'},
            "start": datetime.datetime(2015,8,4,17,0,0),
            "end": datetime.datetime(2015,8,4,19,0,0),
            "area": 270.0, "latitude": 47.5, "longitude": -121.6, "utc_offset": -7.0,
            "plumerise": {
                "2015-08-04T17:00:00": {
                    # "emission_fractions": [0.4, 0.2, 0.2, 0.2],
                    # "emission_fractions": [0.1, 0.3, 0.4,0.2],
                    "emission_fractions": [0.34, 0.22, 0.4, 0.04],
                    "heights": [90.0, 192.5, 295.0, 397.5, 500.0],
                    # need to write out how the result is calculated so that
                    # we have the same rounding error in expected as we have
                    # in actual
                    "smolder_fraction": (0.05*10 + 0.06*2.5) / 12.5  # == 0.052
                },
                "2015-08-04T18:00:00": {
                    "emission_fractions": [0.5, 0.2, 0.2, 0.1],
                    "heights": [300, 350, 400, 425, 450],
                    "smolder_fraction": 0.05
                }
            },
            "timeprofiled_area": {
                "2015-08-04T17:00:00": 32.0,
                "2015-08-04T18:00:00": 30.0

            },
            "timeprofiled_emissions": {
                "2015-08-04T17:00:00": {"CO": 23.0, "PM2.5": 12.5},
                "2015-08-04T18:00:00": {"CO": 3.0, "PM2.5": 5.0}
            },
            "consumption": {
                "flaming": 1511,
                "residual": 1549,
                "smoldering": 1317,
                "total": 4427
            },
            "heat": 3000000.0
        })
        actual = self.merger._merge_fires([
            copy.deepcopy(self.FIRE_1),
            copy.deepcopy(self.FIRE_2)
        ])
        assert actual == expected

    def test_merge_three(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')
        expected = Fire({
            "id": "1234abcd",
            "original_fire_ids": {"bbb", "ddd", "eee"},
            "meta": {'foo': 'bar', 'bar': 'CONFLICT'},
            "start": datetime.datetime(2015,8,4,17,0,0),
            "end": datetime.datetime(2015,8,4,23,0,0),
            # TODO: fill in ....
        })
        actual = self.merger._merge_fires([
            copy.deepcopy(self.FIRE_1),
            copy.deepcopy(self.FIRE_2),
            copy.deepcopy(self.FIRE_3)
        ])
Пример #30
0
class TestFireMerger(object):

    FIRE_1 = Fire({
        "id": "SF11C14225236095807750-0",
        "original_fire_ids": {"SF11C14225236095807750"},
        "meta": {'foo': 'bar'},
        "start": datetime.datetime(2015,8,4,17,0,0),
        "end": datetime.datetime(2015,8,4,19,0,0),
        "area": 120.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T17:00:00": PLUMERISE_HOUR,
            "2015-08-04T18:00:00": EMPTY_PLUMERISE_HOUR
        },
        "timeprofiled_area": {
            "2015-08-04T17:00:00": 12.0,
            "2015-08-04T18:00:00": 0.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T17:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
            "2015-08-04T18:00:00": {"CO": 0.0, 'PM2.5': 0.0}
        },
        "consumption": CONSUMPTION['summary'],
        "heat": 1000000.0
    })

    # no conflicting meta, same location, but overlapping time window
    FIRE_OVERLAPPING_TIME_WINDOWS = Fire({
        "id": "SF11C14225236095807750-0",
        "original_fire_ids": {"SF11C14225236095807750"},
        "meta": {'foo': 'bar'},
        "start": datetime.datetime(2015,8,4,18,0,0),
        "end": datetime.datetime(2015,8,4,20,0,0),
        "area": 120.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T18:00:00": PLUMERISE_HOUR,
            "2015-08-04T19:00:00": EMPTY_PLUMERISE_HOUR
        },
        "timeprofiled_area": {
            "2015-08-04T18:00:00": 12.0,
            "2015-08-04T19:00:00": 0.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T18:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
            "2015-08-04T19:00:00": {"CO": 0.0, 'PM2.5': 0.0}
        },
        "consumption": CONSUMPTION['summary'],
        "heat": 2000000.0
    })

    # contiguous time windows, no conflicting meta, same location
    FIRE_CONTIGUOUS_TIME_WINDOWS = Fire({
        "id": "SF11C14225236095807750-0",
        "original_fire_ids": {"SF11C14225236095807750"},
        "meta": {'foo': 'bar', 'bar': 'asdasd'},
        "start": datetime.datetime(2015,8,4,19,0,0),
        "end": datetime.datetime(2015,8,4,21,0,0),
        "area": 100.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T19:00:00": PLUMERISE_HOUR,
            "2015-08-04T20:00:00": EMPTY_PLUMERISE_HOUR
        },
        "timeprofiled_area": {
            "2015-08-04T19:00:00": 10.0,
            "2015-08-04T20:00:00": 0.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T19:00:00": {"CO": 0.0, "PM2.5": 5.0},  # == 10.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
            "2015-08-04T20:00:00": {"CO": 0.0, 'PM2.5': 0.0}
        },
        "consumption": CONSUMPTION['summary'],
        "heat": 3000000.0
    })

    # non contiguous time windows, no conflicting meta, same location
    FIRE_NON_CONTIGUOUS_TIME_WINDOWS = Fire({
        "id": "SF11C14225236095807750-0",
        "original_fire_ids": {"SF11C14225236095807750"},
        "meta": {'foo': 'bar', 'bar': 'sdf'},
        "start": datetime.datetime(2015,8,4,20,0,0),
        "end": datetime.datetime(2015,8,4,22,0,0),
        "area": 120.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T20:00:00": PLUMERISE_HOUR,
            "2015-08-04T21:00:00": EMPTY_PLUMERISE_HOUR
        },
        "timeprofiled_area": {
            "2015-08-04T20:00:00": 12.0,
            "2015-08-04T21:00:00": 0.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T20:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
            "2015-08-04T21:00:00": {"CO": 0.0, 'PM2.5': 0.0}
        },
        "consumption": CONSUMPTION['summary'],
        "heat": 4000000.0
    })

    FIRE_CONFLICTING_META = Fire({
        "id": "SF11C14225236095807750-0",
        "original_fire_ids": {"SF11C14225236095807750"},
        "meta": {'foo': 'baz'},
        "start": datetime.datetime(2015,8,4,20,0,0),
        "end": datetime.datetime(2015,8,4,22,0,0),
        "area": 120.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T20:00:00": PLUMERISE_HOUR,
            "2015-08-04T21:00:00": EMPTY_PLUMERISE_HOUR
        },
        "timeprofiled_area": {
            "2015-08-04T20:00:00": 12.0,
            "2015-08-04T21:00:00": 0.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T20:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
            "2015-08-04T21:00:00": {"CO": 0.0, 'PM2.5': 0.0}
        },
        "consumption": CONSUMPTION['summary'],
        "heat": 5000000.0
    })

    FIRE_DIFFERENT_LAT_LNG = Fire({
        "id": "SF11C14225236095807750-0",
        "original_fire_ids": {"SF11C14225236095807750"},
        "meta": {},
        "start": datetime.datetime(2015,8,4,20,0,0),
        "end": datetime.datetime(2015,8,4,22,0,0),
        "area": 120.0, "latitude": 47.0, "longitude": -121.0, "utc_offset": -7.0,
        "plumerise": {
            "2015-08-04T20:00:00": PLUMERISE_HOUR,
            "2015-08-04T21:00:00": EMPTY_PLUMERISE_HOUR
        },
        "timeprofiled_area": {
            "2015-08-04T20:00:00": 12.0,
            "2015-08-04T21:00:00": 0.0
        },
        "timeprofiled_emissions": {
            "2015-08-04T20:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
            "2015-08-04T21:00:00": {"CO": 0.0, 'PM2.5': 0.0}
        },
        "consumption": CONSUMPTION['summary'],
        "heat": 6000000.0
    })

    # def setup(self):
    #     pass

    ## Cases that do *not* merge

    def test_one_fire(self):
        original_fire_1 = copy.deepcopy(self.FIRE_1)
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1])

        assert len(merged_fires) == 1
        assert merged_fires == [self.FIRE_1]

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1

    def test_differenent_lat_lng(self):
        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_fire_different_lat_lng = copy.deepcopy(self.FIRE_DIFFERENT_LAT_LNG)

        # shouldn't be merged
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1, self.FIRE_DIFFERENT_LAT_LNG])

        assert len(merged_fires) == 2
        assert merged_fires == [self.FIRE_1, self.FIRE_DIFFERENT_LAT_LNG]

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_DIFFERENT_LAT_LNG == original_fire_different_lat_lng

    def test_overlapping_time_windows(self):
        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_overlapping_time_windows = copy.deepcopy(self.FIRE_OVERLAPPING_TIME_WINDOWS)

        # shouldn't be merged
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1, self.FIRE_OVERLAPPING_TIME_WINDOWS])

        assert len(merged_fires) == 2
        assert merged_fires == [self.FIRE_1, self.FIRE_OVERLAPPING_TIME_WINDOWS]

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_OVERLAPPING_TIME_WINDOWS == original_overlapping_time_windows

    def test_conflicting_meta(self):
        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_fire_conflicting_meta = copy.deepcopy(self.FIRE_CONFLICTING_META)

        # shouldn't be merged
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1, self.FIRE_CONFLICTING_META])

        assert len(merged_fires) == 2
        assert merged_fires == [self.FIRE_1, self.FIRE_CONFLICTING_META]

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_CONFLICTING_META == original_fire_conflicting_meta

    ## Cases that merge

    def test_non_contiguous_time_windows(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')

        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_fire_non_contiguous_time_windows = copy.deepcopy(self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS)

        # *should* be merged
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1, self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS])

        expected_merged_fires = [
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'sdf'},
                "start": datetime.datetime(2015,8,4,17,0,0),
                "end": datetime.datetime(2015,8,4,22,0,0),
                "area": 240.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T17:00:00": PLUMERISE_HOUR,
                    "2015-08-04T18:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": PLUMERISE_HOUR,
                    "2015-08-04T21:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T17:00:00": 12.0,
                    "2015-08-04T18:00:00": 0.0,
                    "2015-08-04T20:00:00": 12.0,
                    "2015-08-04T21:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T17:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T18:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T20:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T21:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 5000000.0
            })
        ]

        assert len(merged_fires) == len(expected_merged_fires)
        assert merged_fires == expected_merged_fires

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS == original_fire_non_contiguous_time_windows

    def test_contiguous_time_windows(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')

        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_fire_contiguous_time_windows = copy.deepcopy(self.FIRE_CONTIGUOUS_TIME_WINDOWS)

        # *should* be merged
        merged_fires = firemerge.FireMerger().merge([self.FIRE_1, self.FIRE_CONTIGUOUS_TIME_WINDOWS])

        expected_merged_fires = [
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'asdasd'},
                "start": datetime.datetime(2015,8,4,17,0,0),
                "end": datetime.datetime(2015,8,4,21,0,0),
                "area": 220.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T17:00:00": PLUMERISE_HOUR,
                    "2015-08-04T18:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T19:00:00": PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T17:00:00": 12.0,
                    "2015-08-04T18:00:00": 0.0,
                    "2015-08-04T19:00:00": 10.0,
                    "2015-08-04T20:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T17:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T18:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T19:00:00": {"CO": 0.0, "PM2.5": 5.0},  # == 10.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T20:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 4000000.0
            })
        ]

        assert len(merged_fires) == len(expected_merged_fires)
        assert merged_fires == expected_merged_fires

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_CONTIGUOUS_TIME_WINDOWS == original_fire_contiguous_time_windows

    def test_all(self, monkeypatch):
        monkeypatch.setattr(uuid, 'uuid4', lambda: '1234abcd')

        original_fire_1 = copy.deepcopy(self.FIRE_1)
        original_overlapping_time_windows = copy.deepcopy(self.FIRE_OVERLAPPING_TIME_WINDOWS)
        original_fire_contiguous_time_windows = copy.deepcopy(self.FIRE_CONTIGUOUS_TIME_WINDOWS)
        original_fire_non_contiguous_time_windows = copy.deepcopy(self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS)
        original_fire_conflicting_meta = copy.deepcopy(self.FIRE_CONFLICTING_META)
        original_fire_different_lat_lng = copy.deepcopy(self.FIRE_DIFFERENT_LAT_LNG)

        merged_fires = firemerge.FireMerger().merge([
            self.FIRE_1,
            self.FIRE_OVERLAPPING_TIME_WINDOWS,
            self.FIRE_CONTIGUOUS_TIME_WINDOWS,
            self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS,
            self.FIRE_CONFLICTING_META,
            self.FIRE_DIFFERENT_LAT_LNG
        ])

        expected_merged_fires = [
            # FIRE_1 merged with FIRE_CONTIGUOUS_TIME_WINDOWS
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'asdasd'},
                "start": datetime.datetime(2015,8,4,17,0,0),
                "end": datetime.datetime(2015,8,4,21,0,0),
                "area": 220.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T17:00:00": PLUMERISE_HOUR,
                    "2015-08-04T18:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T19:00:00": PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T17:00:00": 12.0,
                    "2015-08-04T18:00:00": 0.0,
                    "2015-08-04T19:00:00": 10.0,
                    "2015-08-04T20:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T17:00:00": {"CO": 0.0, "PM2.5": 4.0},  # == 5.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T18:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T19:00:00": {"CO": 0.0, "PM2.5": 5.0},  # == 10.0 * 0.2 + 10.0 * 0.1 + 20.0 * 0.1
                    "2015-08-04T20:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 4000000.0
            }),
            # FIRE_OVERLAPPING_TIME_WINDOWS merged with
            # FIRE_NON_CONTIGUOUS_TIME_WINDOWS
            Fire({
                "id": "1234abcd",
                "original_fire_ids": {"SF11C14225236095807750"},
                "meta": {'foo': 'bar', 'bar': 'sdf'},
                "start": datetime.datetime(2015,8,4,18,0,0),
                "end": datetime.datetime(2015,8,4,22,0,0),
                "area": 240.0, "latitude": 47.41, "longitude": -121.41, "utc_offset": -7.0,
                "plumerise": {
                    "2015-08-04T18:00:00": PLUMERISE_HOUR,
                    "2015-08-04T19:00:00": EMPTY_PLUMERISE_HOUR,
                    "2015-08-04T20:00:00": PLUMERISE_HOUR,
                    "2015-08-04T21:00:00": EMPTY_PLUMERISE_HOUR
                },
                "timeprofiled_area": {
                    "2015-08-04T18:00:00": 12.0,
                    "2015-08-04T19:00:00": 0.0,
                    "2015-08-04T20:00:00": 12.0,
                    "2015-08-04T21:00:00": 0.0
                },
                "timeprofiled_emissions": {
                    "2015-08-04T18:00:00": {"CO": 0.0, "PM2.5": 4.0},
                    "2015-08-04T19:00:00": {"CO": 0.0, 'PM2.5': 0.0},
                    "2015-08-04T20:00:00": {"CO": 0.0, "PM2.5": 4.0},
                    "2015-08-04T21:00:00": {"CO": 0.0, 'PM2.5': 0.0}
                },
                "consumption": {k: 2*v for k,v in CONSUMPTION['summary'].items()},
                "heat": 6000000.0
            }),
            self.FIRE_CONFLICTING_META,
            self.FIRE_DIFFERENT_LAT_LNG
        ]

        assert len(merged_fires) == len(expected_merged_fires)
        assert merged_fires == expected_merged_fires

        # make sure input fire wasn't modified
        assert self.FIRE_1 == original_fire_1
        assert self.FIRE_OVERLAPPING_TIME_WINDOWS == original_overlapping_time_windows
        assert self.FIRE_CONTIGUOUS_TIME_WINDOWS == original_fire_contiguous_time_windows
        assert self.FIRE_NON_CONTIGUOUS_TIME_WINDOWS == original_fire_non_contiguous_time_windows
        assert self.FIRE_CONFLICTING_META == original_fire_conflicting_meta
        assert self.FIRE_DIFFERENT_LAT_LNG == original_fire_different_lat_lng