def test_datatype2(self): poly_set = PolygonSet(dtype=np.float32) poly_set.append(p1) poly_set.append(p2) print poly_set[0] print poly_set[0].dtype assert poly_set[0].dtype == np.float32
def test_append(self): # this passes as long as there is no error! poly_set = PolygonSet() poly_set.append(p1) poly_set.append(p2)
def test_bbox(self): poly_set = PolygonSet() poly_set.append(p1) poly_set.append(p2) bb = np.array(((1., 2.), (35., 40.)), dtype=np.float) assert np.array_equal(poly_set.bounding_box, bb)
def deserialize(self, cstruct): appstruct = super(PolygonSetSchema, self).deserialize(cstruct) if len(appstruct) == 0: appstruct = [(-360, -90), (-360, 90), (360, 90), (360, -90)] ps = PolygonSet() for poly in appstruct: ps.append(poly) return ps
def _polygon_set_from_points(self, poly): ''' create PolygonSet() object from list of polygons which in turn is a list of points :returns: PolygonSet() object ''' x = PolygonSet() for p in poly: x.append(p) return x
def test_basemap_square3(dump): p1 = Polygon([[0, 45], [1, 45], [1, 46], [0, 46]], metadata=('name' , 'land', '1')) poly_set = PolygonSet() poly_set.append(p1) gmap = map_canvas.MapCanvas((300, 100), land_polygons=poly_set) gmap.draw_background() gmap.save_background(os.path.join(dump, 'background_square3.png')) assert True
def test_basemap_square(): p1 = Polygon([[0, 45], [1, 45], [1, 46], [0, 46]], metadata=('name' , 'land', '1')) poly_set = PolygonSet() poly_set.append(p1) gmap = map_canvas.MapCanvas((300, 300), land_polygons=poly_set) gmap.draw_background() gmap.save_background(os.path.join(basedir, 'background_square.png')) assert True
def spillable_area_update_from_dict(self, poly_set): 'convert list of tuples back to numpy array' # since metadata will not match, let's create a new PolygonSet, # check equality on _PointsArray and update if not equal ps = PolygonSet() for poly in poly_set: ps.append(poly) if not np.array_equal(self.spillable_area._PointsArray, ps._PointsArray): self.spillable_area = ps return True return False
def deserialize(self, cstruct): if cstruct is None: return None else: appstruct = super(PolygonSetSchema, self).deserialize(cstruct) if len(appstruct) == 0: # empty --should be None return None # fixme: is there any need to for a PolygonSet here? # a list of lists would work fine. # a PolygonSet is created in the spillable_area.setter anyway. ps = PolygonSet() for poly in appstruct: ps.append(poly) return ps
def test_zero_length_false(self): """ A length-zero Polygon set should be Falsey """ poly_set = PolygonSet() assert not poly_set
def test_append(self): # this passes as long as there is no error! poly_set = PolygonSet() poly_set.append(p1) poly_set.append(p2) poly_set.append(p3)
def __init__(self, map_bounds=None, spillable_area=None, name=None): """ This __init__ will be different for other implementations Optional parameters (kwargs) :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster :param spillable_area: The PolygonSet bounding the spillable_area. :type spillable_area: Either a PolygonSet object or a list of lists from which a polygon set can be created. Each element in the list is a list of points defining a polygon. Note on 'map_bounds': ( (x1,y1), (x2,y2),(x3,y3),..) An NX2 array of points that describe a polygon if no map bounds is provided -- the whole world is valid """ if map_bounds is not None: self.map_bounds = np.asarray(map_bounds, dtype=np.float64).reshape(-1, 2) else: # using -360 to 360 to allow stuff to cross the dateline.. self.map_bounds = np.array( ((-360, 90), (360, 90), (360, -90), (-360, -90)), dtype=np.float64) if spillable_area is None: #self.spillable_area = self.map_bounds self.spillable_area = PolygonSet() self.spillable_area.append(self.map_bounds) else: if not isinstance(spillable_area, PolygonSet): spillable_area = self._polygon_set_from_points(spillable_area) self.spillable_area = spillable_area
def __init__(self, map_bounds=None, spillable_area=None, name=None): """ This __init__ will be different for other implementations Optional parameters (kwargs) :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster :param spillable_area: The PolygonSet bounding the spillable_area. :type spillable_area: Either a PolygonSet object or a list of lists from which a polygon set can be created. Each element in the list is a list of points defining a polygon. Note on 'map_bounds': ( (x1,y1), (x2,y2),(x3,y3),..) An NX2 array of points that describe a polygon if no map bounds is provided -- the whole world is valid """ if map_bounds is not None: self.map_bounds = np.asarray(map_bounds, dtype=np.float64).reshape(-1, 2) else: # using -360 to 360 to allow stuff to cross the dateline.. self.map_bounds = np.array(((-360, 90), (360, 90), (360, -90), (-360, -90)), dtype=np.float64) if spillable_area is None: #self.spillable_area = self.map_bounds self.spillable_area = PolygonSet() self.spillable_area.append(self.map_bounds) else: if not isinstance(spillable_area, PolygonSet): spillable_area = self._polygon_set_from_points(spillable_area) self.spillable_area = spillable_area
def __init__(self, filename, refloat_halflife, raster_size=1024 * 1024, **kwargs): """ Creates a GnomeMap (specifically a RasterMap) from a bna file. It is expected that you will get the spillable area and map bounds from the BNA -- if they exist Required arguments: :param bna_file: full path to a bna file :param refloat_halflife: the half-life (in hours) for the re-floating. :param raster_size: the total number of pixels (bytes) to make the raster -- the actual size will match the aspect ratio of the bounding box of the land Optional arguments (kwargs): :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster :param spillable_area: The polygon bounding the spillable_area :param id: unique ID of the object. Using UUID as a string. This is only used when loading object from save file. :type id: string """ self.filename = filename polygons = haz_files.ReadBNA(filename, 'PolygonSet') map_bounds = None spillable_area = None # find the spillable area and map bounds: # and create a new polygonset without them # fixme -- adding a "pop" method to PolygonSet might be better # or a gnome_map_data object... just_land = PolygonSet() # and lakes.... for p in polygons: if p.metadata[1].lower() == 'spillablearea': spillable_area = p elif p.metadata[1].lower() == 'map bounds': map_bounds = p else: just_land.append(p) # now draw the raster map with a map_canvas: # determine the size: BB = just_land.bounding_box # create spillable area and bounds if they weren't in the BNA if map_bounds is None: map_bounds = BB.AsPoly() if spillable_area is None: spillable_area = map_bounds # user defined spillable_area, map_bounds overrides data obtained # from polygons spillable_area = kwargs.pop('spillable_area', spillable_area) map_bounds = kwargs.pop('map_bounds', map_bounds) # stretch the bounding box, to get approximate aspect ratio in # projected coords. aspect_ratio = np.cos(BB.Center[1] * np.pi / 180) \ * (BB.Width / BB.Height) w = int(np.sqrt(raster_size * aspect_ratio)) h = int(raster_size / w) canvas = BW_MapCanvas((w, h), land_polygons=just_land) canvas.draw_background() # canvas.save_background("raster_map_test.png") # # get the bitmap as a numpy array: bitmap_array = canvas.as_array() # __init__ the RasterMap # hours RasterMap.__init__(self, refloat_halflife, bitmap_array, canvas.projection, map_bounds=map_bounds, spillable_area=spillable_area, **kwargs) return None
assert thinned == poly3 def test_thin32(): """ some scaling should remove one point """ thinned = poly3.thin(scale=(5.1, 5.1)) assert len(thinned) == 3 # start and end points should still be same assert np.array_equal(thinned[0], thinned[-1]) ## test on PolygonSet: pset = PolygonSet() pset.append(poly1) pset.append(poly2) pset.append(poly3) def test_thin_set(): thinned = pset.thin(scale=(0.1, 0.1)) assert len(thinned) == 2 assert thinned[0] == poly1.thin(scale=(0.1, 0.1)) assert thinned[1] == poly2.thin(scale=(0.1, 0.1)) def test_larger(): filename = os.path.join(
class GnomeMap(Serializable): """ The very simplest map for GNOME -- all water with only a bounding box for the map bounds. This also serves as a description of the interface """ _update = ['map_bounds', 'spillable_area'] _create = [] _create.extend(_update) _state = copy.deepcopy(Serializable._state) _state.add(save=_create, update=_update) _schema = GnomeMapSchema refloat_halflife = None # note -- no land, so never used def __init__(self, map_bounds=None, spillable_area=None, name=None): """ This __init__ will be different for other implementations Optional parameters (kwargs) :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster :param spillable_area: The PolygonSet bounding the spillable_area. :type spillable_area: Either a PolygonSet object or a list of lists from which a polygon set can be created. Each element in the list is a list of points defining a polygon. Note on 'map_bounds': ( (x1,y1), (x2,y2),(x3,y3),..) An NX2 array of points that describe a polygon if no map bounds is provided -- the whole world is valid """ if map_bounds is not None: self.map_bounds = np.asarray(map_bounds, dtype=np.float64).reshape(-1, 2) else: # using -360 to 360 to allow stuff to cross the dateline.. self.map_bounds = np.array(((-360, 90), (360, 90), (360, -90), (-360, -90)), dtype=np.float64) if spillable_area is None: #self.spillable_area = self.map_bounds self.spillable_area = PolygonSet() self.spillable_area.append(self.map_bounds) else: if not isinstance(spillable_area, PolygonSet): spillable_area = self._polygon_set_from_points(spillable_area) self.spillable_area = spillable_area def _polygon_set_from_points(self, poly): ''' create PolygonSet() object from list of polygons which in turn is a list of points :returns: PolygonSet() object ''' x = PolygonSet() for p in poly: x.append(p) return x def _attr_array_to_dict(self, np_array): '''convert np_array to list of tuples, used for map_bounds, spillable_area''' return map(tuple, np_array.tolist()) def _attr_from_list_to_array(self, l_): ''' dict returned as list of tuples to be converted to numpy array Again used to update_from_dict map_bounds and spillable_area ''' return np.asarray(l_, dtype=np.float64).reshape(-1, 2) def map_bounds_to_dict(self): 'convert numpy array to a list for serializing' return self._attr_array_to_dict(self.map_bounds) def map_bounds_update_from_dict(self, val): 'convert list of tuples back to numpy array' new_arr = self._attr_from_list_to_array(val) if np.any(self.map_bounds != new_arr): self.map_bounds = new_arr return True return False def spillable_area_to_dict(self): 'convert numpy array to a list for serializing' #return self._attr_array_to_dict(self.spillable_area) x = [] for poly in self.spillable_area: x.append(poly.points.tolist()) return x def spillable_area_update_from_dict(self, poly_set): 'convert list of tuples back to numpy array' # since metadata will not match, let's create a new PolygonSet, # check equality on _PointsArray and update if not equal ps = PolygonSet() for poly in poly_set: ps.append(poly) if not np.array_equal(self.spillable_area._PointsArray, ps._PointsArray): self.spillable_area = ps return True return False def on_map(self, coords): """ :param coords: location for test. :type coords: 3-tuple of floats: (long, lat, depth) or a NX3 numpy array :return: bool array: True if the location is on the map, False otherwise Note: coord is 3-d, but the concept of "on the map" is 2-d in this context, so depth is ignored. """ coords = np.asarray(coords, dtype=world_point_type) on_map_mask = points_in_poly(self.map_bounds, coords) return on_map_mask def on_land(self, coord): """ :param coord: location for test. :type coord: 3-tuple of floats: (long, lat, depth) :return: - Always returns False-- no land in this implementation """ return False def in_water(self, coords): """ :param coords: location for test. :type coords: 3-tuple of floats: (long, lat, depth) or an Nx3 array :returns: - True if the point is in the water, - False if the point is on land (or off map?) This implementation has no land, so always True in on the map. """ return self.on_map(coords) def allowable_spill_position(self, coord): """ :param coord: location for test. :type coord: 3-tuple of floats: (long, lat, depth) :return: - True if the point is an allowable spill position - False if the point is not an allowable spill position .. note:: it could be either off the map, or in a location that spills aren't allowed """ for poly in self.spillable_area: if points_in_poly(poly.points, coord): return True return False def _set_off_map_status(self, spill): """ Determines which LEs moved off the map Called by beach_elements after checking for land-hits :param spill: current SpillContainer :type spill: :class:`gnome.spill_container.SpillContainer` """ next_positions = spill['next_positions'] status_codes = spill['status_codes'] off_map = np.logical_not(self.on_map(next_positions)) # let model decide if we want to remove elements marked as off-map status_codes[off_map] = oil_status.off_maps def beach_elements(self, spill): """ Determines which LEs were or weren't beached or moved off_map. status_code is changed to oil_status.off_maps if off the map. Called by the model in the main time loop, after all movers have acted. :param spill: current SpillContainer :type spill: :class:`gnome.spill_container.SpillContainer` This map class has no land, so only the map check and resurface_airborn elements is done: noting else changes. subclasses that override this probably want to make sure that: self.resurface_airborne_elements(spill) self._set_off_map_status(spill) are called. """ self.resurface_airborne_elements(spill) self._set_off_map_status(spill) def refloat_elements(self, spill_container, time_step): """ This method performs the re-float logic -- changing the element status flag, and moving the element to the last known water position :param spill_container: current SpillContainer :type spill_container: :class:`gnome.spill_container.SpillContainer` .. note:: This map class has no land, and so is a no-op. """ pass def resurface_airborne_elements(self, spill_container): """ Takes any elements that are left above the water surface (z < 0.0) and puts them on the surface (z == 0.0) :param spill_container: current SpillContainer :type spill_container: :class:`gnome.spill_container.SpillContainer` .. note:: While this shouldn't occur according to the physics we're modeling, some movers may push elements up too high, or multiple movers may add vertical movement that adds up to over the surface. e.g rise velocity. """ next_positions = spill_container['next_positions'] np.maximum(next_positions[:, 2], 0.0, out=next_positions[:, 2]) return None
class GnomeMap(Serializable): """ The very simplest map for GNOME -- all water with only a bounding box for the map bounds. This also serves as a description of the interface """ _update = ['map_bounds', 'spillable_area'] _create = [] _create.extend(_update) _state = copy.deepcopy(Serializable._state) _state.add(save=_create, update=_update) _schema = GnomeMapSchema refloat_halflife = None # note -- no land, so never used def __init__(self, map_bounds=None, spillable_area=None, name=None): """ This __init__ will be different for other implementations Optional parameters (kwargs) :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster :param spillable_area: The PolygonSet bounding the spillable_area. :type spillable_area: Either a PolygonSet object or a list of lists from which a polygon set can be created. Each element in the list is a list of points defining a polygon. Note on 'map_bounds': ( (x1,y1), (x2,y2),(x3,y3),..) An NX2 array of points that describe a polygon if no map bounds is provided -- the whole world is valid """ if map_bounds is not None: self.map_bounds = np.asarray(map_bounds, dtype=np.float64).reshape(-1, 2) else: # using -360 to 360 to allow stuff to cross the dateline.. self.map_bounds = np.array( ((-360, 90), (360, 90), (360, -90), (-360, -90)), dtype=np.float64) if spillable_area is None: #self.spillable_area = self.map_bounds self.spillable_area = PolygonSet() self.spillable_area.append(self.map_bounds) else: if not isinstance(spillable_area, PolygonSet): spillable_area = self._polygon_set_from_points(spillable_area) self.spillable_area = spillable_area def _polygon_set_from_points(self, poly): ''' create PolygonSet() object from list of polygons which in turn is a list of points :returns: PolygonSet() object ''' x = PolygonSet() for p in poly: x.append(p) return x def _attr_array_to_dict(self, np_array): '''convert np_array to list of tuples, used for map_bounds, spillable_area''' return map(tuple, np_array.tolist()) def _attr_from_list_to_array(self, l_): ''' dict returned as list of tuples to be converted to numpy array Again used to update_from_dict map_bounds and spillable_area ''' return np.asarray(l_, dtype=np.float64).reshape(-1, 2) def map_bounds_to_dict(self): 'convert numpy array to a list for serializing' return self._attr_array_to_dict(self.map_bounds) def map_bounds_update_from_dict(self, val): 'convert list of tuples back to numpy array' new_arr = self._attr_from_list_to_array(val) if np.any(self.map_bounds != new_arr): self.map_bounds = new_arr return True return False def spillable_area_to_dict(self): 'convert numpy array to a list for serializing' #return self._attr_array_to_dict(self.spillable_area) x = [] for poly in self.spillable_area: x.append(poly.points.tolist()) return x def spillable_area_update_from_dict(self, poly_set): 'convert list of tuples back to numpy array' # since metadata will not match, let's create a new PolygonSet, # check equality on _PointsArray and update if not equal ps = PolygonSet() for poly in poly_set: ps.append(poly) if not np.array_equal(self.spillable_area._PointsArray, ps._PointsArray): self.spillable_area = ps return True return False def on_map(self, coords): """ :param coords: location for test. :type coords: 3-tuple of floats: (long, lat, depth) or a NX3 numpy array :return: bool array: True if the location is on the map, False otherwise Note: coord is 3-d, but the concept of "on the map" is 2-d in this context, so depth is ignored. """ coords = np.asarray(coords, dtype=world_point_type) on_map_mask = points_in_poly(self.map_bounds, coords) return on_map_mask def on_land(self, coord): """ :param coord: location for test. :type coord: 3-tuple of floats: (long, lat, depth) :return: - Always returns False-- no land in this implementation """ return False def in_water(self, coords): """ :param coords: location for test. :type coords: 3-tuple of floats: (long, lat, depth) or an Nx3 array :returns: - True if the point is in the water, - False if the point is on land (or off map?) This implementation has no land, so always True in on the map. """ return self.on_map(coords) def allowable_spill_position(self, coord): """ :param coord: location for test. :type coord: 3-tuple of floats: (long, lat, depth) :return: - True if the point is an allowable spill position - False if the point is not an allowable spill position .. note:: it could be either off the map, or in a location that spills aren't allowed """ for poly in self.spillable_area: if points_in_poly(poly.points, coord): return True return False def _set_off_map_status(self, spill): """ Determines which LEs moved off the map Called by beach_elements after checking for land-hits :param spill: current SpillContainer :type spill: :class:`gnome.spill_container.SpillContainer` """ next_positions = spill['next_positions'] status_codes = spill['status_codes'] off_map = np.logical_not(self.on_map(next_positions)) # let model decide if we want to remove elements marked as off-map status_codes[off_map] = oil_status.off_maps def beach_elements(self, spill): """ Determines which LEs were or weren't beached or moved off_map. status_code is changed to oil_status.off_maps if off the map. Called by the model in the main time loop, after all movers have acted. :param spill: current SpillContainer :type spill: :class:`gnome.spill_container.SpillContainer` This map class has no land, so only the map check and resurface_airborn elements is done: noting else changes. subclasses that override this probably want to make sure that: self.resurface_airborne_elements(spill) self._set_off_map_status(spill) are called. """ self.resurface_airborne_elements(spill) self._set_off_map_status(spill) def refloat_elements(self, spill_container, time_step): """ This method performs the re-float logic -- changing the element status flag, and moving the element to the last known water position :param spill_container: current SpillContainer :type spill_container: :class:`gnome.spill_container.SpillContainer` .. note:: This map class has no land, and so is a no-op. """ pass def resurface_airborne_elements(self, spill_container): """ Takes any elements that are left above the water surface (z < 0.0) and puts them on the surface (z == 0.0) :param spill_container: current SpillContainer :type spill_container: :class:`gnome.spill_container.SpillContainer` .. note:: While this shouldn't occur according to the physics we're modeling, some movers may push elements up too high, or multiple movers may add vertical movement that adds up to over the surface. e.g rise velocity. """ next_positions = spill_container['next_positions'] np.maximum(next_positions[:, 2], 0.0, out=next_positions[:, 2]) return None
def __init__(self, filename, raster_size=1024 * 1024, **kwargs): """ Creates a GnomeMap (specifically a RasterMap) from a bna file. It is expected that you will get the spillable area and map bounds from the BNA -- if they exist Required arguments: :param bna_file: full path to a bna file :param refloat_halflife: the half-life (in hours) for the re-floating. :param raster_size: the total number of pixels (bytes) to make the raster -- the actual size will match the aspect ratio of the bounding box of the land Optional arguments (kwargs): :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster :param spillable_area: The polygon bounding the spillable_area :param id: unique ID of the object. Using UUID as a string. This is only used when loading object from save file. :type id: string """ self.filename = filename polygons = haz_files.ReadBNA(filename, 'PolygonSet') map_bounds = None self.name = kwargs.pop('name', os.path.split(filename)[1]) # find the spillable area and map bounds: # and create a new polygonset without them # fixme -- adding a "pop" method to PolygonSet might be better # or a gnome_map_data object... just_land = PolygonSet() # and lakes.... spillable_area = PolygonSet() for p in polygons: if p.metadata[1].lower() == 'spillablearea': spillable_area.append(p) elif p.metadata[1].lower() == 'map bounds': map_bounds = p else: just_land.append(p) # now draw the raster map with a map_canvas: # determine the size: BB = just_land.bounding_box # create spillable area and bounds if they weren't in the BNA if map_bounds is None: map_bounds = BB.AsPoly() if len(spillable_area) == 0: spillable_area.append(map_bounds) # user defined spillable_area, map_bounds overrides data obtained # from polygons # todo: should there be a check between spillable_area read from BNA # versus what the user entered. if this is within spillable_area for # BNA, then include it? else ignore #spillable_area = kwargs.pop('spillable_area', spillable_area) spillable_area = kwargs.pop('spillable_area', spillable_area) map_bounds = kwargs.pop('map_bounds', map_bounds) # stretch the bounding box, to get approximate aspect ratio in # projected coords. aspect_ratio = (np.cos(BB.Center[1] * np.pi / 180) * (BB.Width / BB.Height)) w = int(np.sqrt(raster_size * aspect_ratio)) h = int(raster_size / w) canvas = BW_MapCanvas((w, h), land_polygons=just_land) canvas.draw_background() # canvas.save_background("raster_map_test.png") # # get the bitmap as a numpy array: bitmap_array = canvas.as_array() # __init__ the RasterMap # hours RasterMap.__init__(self, bitmap_array, canvas.projection, map_bounds=map_bounds, spillable_area=spillable_area, **kwargs) return None
def test_datatype(self): poly_set = PolygonSet(dtype=np.float32) poly_set.append(p1) poly_set.append(p2) assert poly_set.dtype == np.float32
def test_zero_length(self): poly_set = PolygonSet() assert len(poly_set) == 0
""" just enough scaling shouldn't change it """ thinned = poly3.thin(scale=(10, 10) ) assert thinned == poly3 def test_thin32(): """ some scaling should remove one point """ thinned = poly3.thin(scale=(5.1, 5.1) ) assert len(thinned) == 3 # start and end points should still be same assert np.array_equal( thinned[0], thinned[-1] ) ## test on PolygonSet: pset = PolygonSet() pset.append(poly1) pset.append(poly2) pset.append(poly3) def test_thin_set(): thinned = pset.thin( scale=(0.1, 0.1) ) assert len(thinned) == 2 assert thinned[0] == poly1.thin( scale=(0.1, 0.1) ) assert thinned[1] == poly2.thin( scale=(0.1, 0.1) ) def test_larger(): filename = os.path.join( os.path.split(__file__)[0],'00439polys_013685pts.bna' ) polys439 = ReadBNA(filename, polytype = "PolygonSet")