def test_insufficient(self): expected_err_msg = ("Insufficient location data required " "for determining single lat/lng for location") with raises(ValueError) as e_info: locationutils.LatLng({}) assert e_info.value.args[0] == expected_err_msg with raises(ValueError) as e_info: locationutils.LatLng({'latitude': 46.0}) assert e_info.value.args[0] == expected_err_msg
def test_invalid_location_data(self): with raises(ValueError) as e_info: locationutils.LatLng(None) assert e_info.value.args[0] == locationutils.INVALID_LOCATION_DATA with raises(ValueError) as e_info: locationutils.LatLng(1) assert e_info.value.args[0] == locationutils.INVALID_LOCATION_DATA with raises(ValueError) as e_info: locationutils.LatLng("SDFSDF") assert e_info.value.args[0] == locationutils.INVALID_LOCATION_DATA
def test_invalid(self): expected_err_msg = ("Invalid location data required for " "determining single lat/lng for activity window") with raises(ValueError) as e_info: locationutils.LatLng(None) assert e_info.value.args[0] == expected_err_msg with raises(ValueError) as e_info: locationutils.LatLng(1) assert e_info.value.args[0] == expected_err_msg with raises(ValueError) as e_info: locationutils.LatLng("SDFSDF") assert e_info.value.args[0] == expected_err_msg
def test_active_area_with_specified_points_missing_lat_lng_info(self): active_area = { "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00", 'specified_points': [ { 'lat': 23.0, 'lng': "SDF" }, # invalid ] } with raises(ValueError) as e_info: locationutils.LatLng(active_area) assert e_info.value.args[ 0] == locationutils.MISSING_OR_INVALID_LAT_LNG_FOR_SPECIFIED_POINT active_area = { "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00", 'specified_points': [ { 'lng': -120.0 }, # missing 'lat' ] } with raises(ValueError) as e_info: locationutils.LatLng(active_area) assert e_info.value.args[ 0] == locationutils.MISSING_OR_INVALID_LAT_LNG_FOR_SPECIFIED_POINT active_area = { "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00", 'specified_points': [ { 'area': 34, 'lat': 45.0, 'lng': -120.0 }, { 'lat': 23.0 } # missing 'lng' ] } with raises(ValueError) as e_info: locationutils.LatLng(active_area) assert e_info.value.args[ 0] == locationutils.MISSING_OR_INVALID_LAT_LNG_FOR_SPECIFIED_POINT
def _get_central_lat_lng(self): if len(self._input_data['fires']) > 1: # input data could possibly specifu multiple fires at # the same location, but we won't bother trying to accept that self._handle_error(400, ErrorMessages.SINGLE_FIRE_ONLY) fire = fires.Fire(self._input_data['fires'][0]) try: locations = fire.locations except ValueError as e: # fire.locations includes validation of the location data self._handle_error(400, ErrorMessages.INVALID_FIRE_LOCATION_INFO) if not locations: self._handle_error(400, ErrorMessages.NO_ACTIVITY_INFO) centroids = [] for loc in locations: try: latlng = locationutils.LatLng(loc) centroids.append((latlng.latitude, latlng.longitude)) except Exception as e: self._handle_error(400, ErrorMessages.INVALID_FIRE_LOCATION_INFO) if len(centroids) > 1: multi_point = { "type": 'MultiPoint', "coordinates": [[e[1], e[0]] for e in centroids] } coords = get_centroid(multi_point) return (coords[1], coords[0]) else: return centroids[0]
def write(self, start, met_files, working_dir, locations): with open(os.path.join(working_dir, 'CONTROL'), 'w') as f: f.write(start.strftime("%Y %m %d %H\n")) # TODO: is this correct? num_trajectries = len(locations) * len(self._config['heights']) f.write("{}\n".format(num_trajectries)) for loc in locations: latlng = locationutils.LatLng(loc) for h in self._config['heights']: f.write("{} {} {}\n".format(latlng.latitude, latlng.longitude, h)) f.write("{}\n".format(self._num_hours)) f.write("{}\n".format(self._config['vertical_motion'])) f.write("{}\n".format(self._config['top_of_model_domain'])) # TODO: should the next line be configurable instead of beting # set to the number of met files? e.g.: # f.write("{}\n".format(self._config['num_simultaneous_met_files'])) f.write("{}\n".format(len(met_files))) for m in met_files: f.write("./\n") # met grid directory f.write("{}\n".format(os.path.basename(m))) # met grid file (ARL format) f.write("./\n") # output directoroy f.write("{}\n".format(self._config['output_file_name']))
def _set_lat_lng(self, fire): lat_lngs = [locationutils.LatLng(l) for l in fire.locations] def get_min_max(vals): return min(vals), max(vals), sum(vals) / len(vals) min_lat, max_lat, avg_lat = get_min_max([ll.latitude for ll in lat_lngs]) min_lng, max_lng, avg_lng = get_min_max([ll.longitude for ll in lat_lngs]) def format_str(mi, ma): return "{} to {}".format(mi, ma) if mi != ma else mi self['lat_lng'] = { 'lat': { 'min': min_lat, 'max': max_lat, 'avg': avg_lat, 'pretty_str': format_str(min_lat, max_lat), }, 'lng': { 'min': min_lng, 'max': max_lng, 'avg': avg_lng, 'pretty_str': format_str(min_lng, max_lng) } }
def _set_per_location_data(self, fire): self['active_areas'] = [] for i, aa in enumerate(fire.active_areas): # set location ids self['active_areas'].append({ "id": "#{} {} - {}".format(i, aa['start'], aa['end']), "start": aa['start'], "end": aa['end'], 'locations': [] }) for j, loc in enumerate(aa.locations): lat_lng = locationutils.LatLng(loc) emissions = self._get_location_emissions(loc) timeprofiled_emissions = self._get_location_timeprofiled_emissions( aa, loc, emissions) self['active_areas'][-1]['locations'].append({ "start": aa["start"], "end": aa["end"], "lat": lat_lng.latitude, "lng": lat_lng.longitude, "area": loc["area"], "id": "#{}/#{} {},{}".format( i, j, lat_lng.latitude, lat_lng.longitude), "fuelbeds": self.fuelbeds_list_to_dict( fuelbeds.summarize([self._wrap_loc_in_fire(loc)])), 'consumption_by_category': self._get_location_consumption(loc), "emissions": emissions, 'timeprofiled_emissions': timeprofiled_emissions, "plumerise": self._get_location_plumerise(loc) })
def _get_centroid(self, fires): points = [{ 'lng': float(f.longitude), 'lat': float(f.latitude) } for f in fires] return locationutils.LatLng({'specified_points': points})
def test_valid_perimeter(self): latlng = locationutils.LatLng({ "polygon": [[-100.0, 35.0], [-101.0, 35.0], [-101.0, 31.0], [-99.0, 31.0], [-100.0, 35.0]] }) assert latlng.latitude == 33 assert latlng.longitude == -100.25
def test_no_location_info(self): active_area = { "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00" } with raises(ValueError) as e_info: locationutils.LatLng(active_area) assert e_info.value.args[0] == locationutils.MISSING_LOCATION_INFO
def test_geojson_point(self): latlng = locationutils.LatLng({ "geojson": { "type": "Point", "coordinates": [-121.4522115, 47.4316976] } }) assert latlng.latitude == 47.4316976 assert latlng.longitude == -121.4522115
def test_active_area_with_perimeter(self): latlng = locationutils.LatLng({ "perimeter": { "polygon": [[-100.0, 35.0], [-101.0, 35.0], [-101.0, 31.0], [-99.0, 31.0], [-100.0, 35.0]] } }) assert latlng.latitude == 33 assert latlng.longitude == -100.25
def test_geojson_multi_point(self): latlng = locationutils.LatLng({ "geojson": { "type": "MultiPoint", "coordinates": [[100.0, 4.5], [101.0, 1.0]] } }) assert latlng.latitude == 2.75 assert latlng.longitude == 100.5
def test_geojson_linestring(self): latlng = locationutils.LatLng({ "geojson": { "type": "LineString", "coordinates": [[100.0, 2.0], [101.0, 1.0]] } }) assert latlng.latitude == 1.5 assert latlng.longitude == 100.5
def test_perimeter_invalid_coordinates(self): perimeter = { "polygon": [["SDF", 47.43], [-121.39, 47.43], [-121.39, 47.40], [-121.45, 47.40], [-121.45, 47.43]] } with raises(ValueError) as e_info: locationutils.LatLng(perimeter) assert e_info.value.args[ 0] == locationutils.MISSING_OR_INVALID_COORDINATES_FOR_PERIMETER
def test_single_lat_lng(self): latlng = locationutils.LatLng({ "utc_offset": "-07:00", "longitude": -119.7615805, "area": 10000, "ecoregion": "western", "latitude": 37.909644 }) assert latlng.latitude == 37.909644 assert latlng.longitude == -119.7615805
def test_geojson_polygon_no_holes(self): latlng = locationutils.LatLng({ "geojson": { "type": "Polygon", "coordinates": [[[100.0, 5.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 5.0]]] } }) assert latlng.latitude == 2.4 assert latlng.longitude == 100.4
def test_geojson_multi_linestring(self): latlng = locationutils.LatLng({ "geojson": { "type": "MultiLineString", "coordinates": [[[100.0, -3.0], [101.0, 1.0]], [[102.0, 2.0], [103.0, 3.0]]] } }) assert latlng.latitude == 0.75 assert latlng.longitude == 101.5
def _aggregate_locations(self): locations_by_lat_lng = defaultdict(lambda: {"lines": []}) for fire in self._fires_manager.fires: for loc in fire.locations: latlng = locationutils.LatLng(loc) new_loc = locations_by_lat_lng[(latlng.latitude, latlng.longitude)] # The following line only has to be done if not # new_loc.get('fire'), but doesn't hurt to do again new_loc.update(fire=fire, latlng=latlng) new_loc['lines'].extend( loc.get('trajectories', {}).get('lines', [])) self._distinct_locations = list(locations_by_lat_lng.values())
def test_active_area_with_perimeter_invalid_coordinates(self): active_area = { "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00", "perimeter": { "polygon": [["SDF", 47.43], [-121.39, 47.43], [-121.39, 47.40], [-121.45, 47.40], [-121.45, 47.43]] } } with raises(ValueError) as e_info: locationutils.LatLng(active_area) assert e_info.value.args[ 0] == locationutils.MISSING_OR_INVALID_COORDINATES_FOR_PERIMETER
def _f(fire, working_dir): # TODO: create and change to working directory here (per fire), # above (one working dir per all fires), or below (per activity # window)...or just let plumerise create temp workingdir (as # it's currently doing? for aa in fire.active_areas: start = aa.get('start') if not start: raise ValueError(MISSING_START_TIME_ERROR_MSG) start = datetimeutils.parse_datetime(aa.get('start'), 'start') if not aa.get('timeprofile'): raise ValueError(MISSING_TIMEPROFILE_ERROR_MSG) for loc in aa.locations: if not loc.get('consumption', {}).get('summary'): raise ValueError(MISSING_CONSUMPTION_ERROR_MSG) # Fill in missing sunrise / sunset if any([ loc.get(k) is None for k in ('sunrise_hour', 'sunset_hour') ]): # default: UTC utc_offset = datetimeutils.parse_utc_offset( loc.get('utc_offset', 0.0)) # Use NOAA-standard sunrise/sunset calculations latlng = locationutils.LatLng(loc) s = sun.Sun(lat=latlng.latitude, lng=latlng.longitude) d = start.date() # just set them both, even if one is already set loc["sunrise_hour"] = s.sunrise_hr(d, utc_offset) loc["sunset_hour"] = s.sunset_hr(d, utc_offset) fire_working_dir = _get_fire_working_dir(fire, working_dir) plumerise_data = pr.compute(aa['timeprofile'], loc['consumption']['summary'], loc, working_dir=fire_working_dir) loc['plumerise'] = plumerise_data['hours'] if config.get("load_heat"): if 'fuelbeds' not in loc: raise ValueError( "Fuelbeds should exist before loading heat in plumerise" ) loc["fuelbeds"][0]["heat"] = _loadHeat( fire_working_dir)
def test_geojson_polygon_holes(self): latlng = locationutils.LatLng({ "geojson": { "type": "Polygon", "coordinates": [[[100.0, 5.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 5.0]], [[100.8, 0.8], [100.8, 0.2], [100.2, 0.2], [100.2, 0.8], [100.8, 0.8]]] } }) # centroid computation only considers outer polygon boundary assert latlng.latitude == 2.4 assert latlng.longitude == 100.4
def test_active_area_with_single_specified_point(self): latlng = locationutils.LatLng({ "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00", 'specified_points': [{ 'area': 34, 'lat': 45.0, 'lng': -120.0 }] }) assert latlng.latitude == 45.0 assert latlng.longitude == -120.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)
def _write_to_json(self): """Maintains bluesky's fires output structure, but including only trajectory data. """ json_data = {"fires": []} for fire in self._fires_manager.fires: json_data["fires"].append({"id": fire.id, "locations": []}) for loc in fire.locations: lines = loc.get('trajectories', {}).get('lines', []) latlng = locationutils.LatLng(loc) json_data["fires"][-1]["locations"].append({ "lines": lines, "lat": latlng.latitude, "lng": latlng.longitude }) self._write_file(self._json_file_name, json_data)
def test_geojson_multi_polygon_one(self): latlng = locationutils.LatLng({ "geojson": { "type": "MultiPolygon", "coordinates": [[[[-121.4522115, 47.4316976], [-121.3990506, 47.4316976], [-121.3990506, 47.4099293], [-121.4522115, 47.4099293], [-121.4522115, 47.4316976]]]] }, "ecoregion": "southern", "state": "KS", "country": "USA", "slope": 20.0, "elevation": 2320.0, "max_humid": 70.0, "utc_offset": "-07:00" }) assert latlng.latitude == 47.4316976 assert latlng.longitude == -121.4522115
def test_geojson_multi_polygon_one(self): latlng = locationutils.LatLng({ "geojson": { "type": "MultiPolygon", "coordinates": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], [[100.2, 0.2], [100.2, 0.8], [100.8, 0.8], [100.8, 0.2], [100.2, 0.2]]]] }, "ecoregion": "southern", "state": "KS", "country": "USA", "slope": 20.0, "elevation": 2320.0, "max_humid": 70.0, "utc_offset": "-07:00" }) assert latlng.latitude == 1.4 assert latlng.longitude == 101.4
def test_active_area_with_specified_points_and_perimeter(self): latlng = locationutils.LatLng({ "start": "2014-05-27T17:00:00", "end": "2014-05-28T17:00:00", 'specified_points': [{ 'area': 34, 'lat': 45.0, 'lng': -120.0 }, { 'area': 20, 'lat': 35.0, 'lng': -121.0 }], "perimeter": { "polygon": [[-100.0, 35.0], [-101.0, 35.0], [-101.0, 31.0], [-99.0, 31.0], [-100.0, 35.0]] } }) assert latlng.latitude == 40.0 assert latlng.longitude == -120.5
def _write_to_geojson(self): """Flattens trajectory data into a FeatureCollection of trajectory line Features (one Feature per line) TODO: group by fire, and then by location, and then by start hour, if possible. """ geojson_data = { "type": "FeatureCollection", "features": [] } for fire in self._fires_manager.fires: for loc in fire.locations: latlng = locationutils.LatLng(loc) geojson_data['features'].append( self._get_location_feature(fire, loc, latlng)) lines = loc.get('trajectories', {}).get('lines', []) for line in lines: geojson_data['features'].append( self._get_line_feature(fire, latlng, line)) self._write_file(self._geojson_file_name, geojson_data)