Ejemplo n.º 1
0
 def get_delta_RK4(self, sc, time_step, model_time, pos, vel_field):
     dt = timedelta(seconds=time_step)
     dt_s = dt.seconds
     t = model_time
     v0 = vel_field.at(pos, t, extrapolate=self.extrapolate).data
     d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s / 2, pos)
     v1 = vel_field.at(pos + d0, t + dt / 2,
                       extrapolate=self.extrapolate).data
     d1 = FlatEarthProjection.meters_to_lonlat(v1 * dt_s / 2, pos)
     v2 = vel_field.at(pos + d1, t + dt / 2,
                       extrapolate=self.extrapolate).data
     d2 = FlatEarthProjection.meters_to_lonlat(v2 * dt_s, pos)
     v3 = vel_field.at(pos + d2, t + dt, extrapolate=self.extrapolate).data
     return dt_s / 6 * (v0 + 2 * v1 + 2 * v2 + v3)
Ejemplo n.º 2
0
    def get_move(self, sc, time_step, model_time_datetime):
        """
        Compute the move in (long,lat,z) space. It returns the delta move
        for each element of the spill as a numpy array of size
        (number_elements X 3) and dtype = gnome.basic_types.world_point_type

        Base class returns an array of numpy.nan for delta to indicate the
        get_move is not implemented yet.

        Each class derived from Mover object must implement it's own get_move

        :param sc: an instance of gnome.spill_container.SpillContainer class
        :param time_step: time step in seconds
        :param model_time_datetime: current model time as datetime object

        All movers must implement get_move() since that's what the model calls
        """
        status = sc['status_codes'] != oil_status.in_water
        positions = sc['positions']

        vels = self.grid.interpolated_velocities(model_time_datetime, positions[:, 0:2])
        deltas = np.zeros_like(positions)
        deltas[:] = 0.
        deltas[:, 0:2] = vels * time_step
        deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions)
        deltas[status] = (0, 0, 0)
        pass
        return deltas
Ejemplo n.º 3
0
def test_uncertainty():
    sp = sample_sc_release(num_elements=1000, start_pos=(0.0, 0.0, 0.0))

    u_sp = sample_sc_release(num_elements=1000,
                             start_pos=(0.0, 0.0, 0.0),
                             uncertain=True)

    mover = simple_mover.SimpleMover(velocity=(10.0, 10.0, 0.0))

    delta = mover.get_move(sp, time_step=100, model_time=None)
    u_delta = mover.get_move(u_sp, time_step=100, model_time=None)

    # expected = np.zeros_like(delta)

    expected = proj.meters_to_lonlat((1000.0, 1000.0, 0.0), (0.0, 0.0, 0.0))

    assert np.alltrue(delta == expected)

    # but uncertain spills should be different:

    assert not np.alltrue(u_delta == expected)

    # the mean should be close:
    # this is teh smallest tolerance that consitantly passed -- good enough?

    assert np.allclose(np.mean(delta, 0), np.mean(u_delta, 0), rtol=1.7e-1)
Ejemplo n.º 4
0
    def get_move(self, sc, time_step, model_time_datetime, num_method=None):
        """
        Compute the move in (long,lat,z) space. It returns the delta move
        for each element of the spill as a numpy array of size
        (number_elements X 3) and dtype = gnome.basic_types.world_point_type

        Base class returns an array of numpy.nan for delta to indicate the
        get_move is not implemented yet.

        Each class derived from Mover object must implement it's own get_move

        :param sc: an instance of gnome.spill_container.SpillContainer class
        :param time_step: time step in seconds
        :param model_time_datetime: current model time as datetime object

        All movers must implement get_move() since that's what the model calls
        """
        positions = sc['positions']

        if self.active and len(positions) > 0:
            status = sc['status_codes'] != oil_status.in_water
            pos = positions[:]

            deltas = self.delta_method(num_method)(sc, time_step, model_time_datetime, pos, self.wind)
            deltas[:, 0] *= sc['windages'] * self.wind_scale
            deltas[:, 1] *= sc['windages'] * self.wind_scale

            deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions)
            deltas[status] = (0, 0, 0)
        else:
            deltas = np.zeros_like(positions)

        return deltas
Ejemplo n.º 5
0
def test_uncertainty():
    sp = sample_sc_release(num_elements=1000, start_pos=(0.0, 0.0,
                            0.0))

    u_sp = sample_sc_release(num_elements=1000, start_pos=(0.0, 0.0,
                              0.0), uncertain=True)

    mover = simple_mover.SimpleMover(velocity=(10.0, 10.0, 0.0))

    delta = mover.get_move(sp, time_step=100, model_time=None)
    u_delta = mover.get_move(u_sp, time_step=100, model_time=None)

    # expected = np.zeros_like(delta)

    expected = proj.meters_to_lonlat((1000.0, 1000.0, 0.0), (0.0, 0.0,
            0.0))

    assert np.alltrue(delta == expected)

    # but uncertain spills should be different:

    assert not np.alltrue(u_delta == expected)

    # the mean should be close:
    # this is teh smallest tolerance that consitantly passed -- good enough?

    assert np.allclose(np.mean(delta, 0), np.mean(u_delta, 0),
                       rtol=1.7e-1)
Ejemplo n.º 6
0
    def get_move(self, sc, time_step, model_time_datetime, num_method = None):
        """
        Compute the move in (long,lat,z) space. It returns the delta move
        for each element of the spill as a numpy array of size
        (number_elements X 3) and dtype = gnome.basic_types.world_point_type

        Base class returns an array of numpy.nan for delta to indicate the
        get_move is not implemented yet.

        Each class derived from Mover object must implement it's own get_move

        :param sc: an instance of gnome.spill_container.SpillContainer class
        :param time_step: time step in seconds
        :param model_time_datetime: current model time as datetime object

        All movers must implement get_move() since that's what the model calls
        """
        method = None
        if num_method is None:
            method = self.num_methods[self.default_num_method]
        else:
            method = self.num_method[num_method]

        status = sc['status_codes'] != oil_status.in_water
        positions = sc['positions']
        deltas = np.zeros_like(positions)
        pos = positions[:, 0:2]

        deltas[:, 0:2] = method(sc, time_step, model_time_datetime, pos, self.wind)
        deltas[:,0] *= sc['windages']
        deltas[:,1] *= sc['windages']

        deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions)
        deltas[status] = (0, 0, 0)
        return deltas
Ejemplo n.º 7
0
    def __init__(self,
                 image_size,
                 projection=None,
                 viewport=None,
                 preset_colors='BW',
                 background_color='transparent',
                 colordepth=8):
        """
        create a new map image from scratch -- specifying the size

        :param image_size: (width, height) tuple of the image size in pixels

        Optional parameters

        :param projection=None: gnome.utilities.projections object to use.
                                if None, it defaults to FlatEarthProjection()

        :param viewport: viewport of map -- what gets drawn and on what
                         scale.
                         Default is full globe: (((-180, -90), (180, 90)))

        :param preset_colors='BW': color set to preset. Options are:

                                   'BW' - transparent, black, and white:
                                          transparent background

                                   'web' - the basic named colors for the web:
                                           transparent background

                                   'transparent' - transparent background,
                                                   no other colors set

                                   None - no pre-allocated colors
                                          -- the first one you allocate will
                                             be the background color

        :param background_color = 'transparent': color for the background
                                                 -- must be a color that exists

        :param colordepth=8: only 8 bit color supported for now
                             maybe someday, 32 bit will be an option
        """
        projection = (FlatEarthProjection()
                      if projection is None else projection)
        self._image_size = image_size

        if colordepth != 8:
            raise NotImplementedError("only 8 bit color currently implemented")

        self.background_color = background_color
        self.create_images(preset_colors)
        self.projection = projection
        self._viewport = Viewport(((-180, -90), (180, 90)))

        if viewport is not None:
            self._viewport.BB = tuple(map(tuple, viewport))

        self.projection.set_scale(self.viewport, self.image_size)
        self.graticule = GridLines(self._viewport, self.projection)
Ejemplo n.º 8
0
 def get_delta_Trapezoid(self, sc, time_step, model_time, pos, vel_field):
     dt = timedelta(seconds=time_step)
     dt_s = dt.seconds
     t = model_time
     v0 = vel_field.at(pos, t, extrapolate=self.extrapolate).data
     d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s, pos)
     v1 = vel_field.at(pos + d0, t + dt, extrapolate=self.extrapolate).data
     return dt_s / 2 * (v0 + v1)
Ejemplo n.º 9
0
def test_north():
    sp = sample_sc_release(num_elements=10, start_pos=(20, 0.0, 0.0))

    mover = simple_mover.SimpleMover(velocity=(0.0, 10, 0.0))

    delta = mover.get_move(sp, time_step=100.0, model_time=None)

    # expected = np.zeros_like(delta)

    expected = proj.meters_to_lonlat((0.0, 1000.0, 0.0), (0.0, 0.0, 0.0))
    assert np.alltrue(delta == expected)
Ejemplo n.º 10
0
    def _expected_move(self):
        """
        Put the expected move logic in separate (fixture) if it gets used
        multiple times
        """
        uv = r_theta_to_uv_wind(self.time_val['value'])
        exp = np.zeros((self.sc.num_released, 3))
        exp[:, 0] = self.sc['windages'] * uv[0, 0] * self.time_step
        exp[:, 1] = self.sc['windages'] * uv[0, 1] * self.time_step

        xform = FlatEarthProjection.meters_to_lonlat(exp, self.sc['positions'])
        return xform
Ejemplo n.º 11
0
def test_north():
    sp = sample_sc_release(num_elements=10, start_pos=(20, 0.0, 0.0))

    mover = simple_mover.SimpleMover(velocity=(0.0, 10, 0.0))

    delta = mover.get_move(sp, time_step=100.0, model_time=None)

    # expected = np.zeros_like(delta)

    expected = proj.meters_to_lonlat((0.0, 1000.0, 0.0), (0.0, 0.0,
            0.0))
    assert np.alltrue(delta == expected)
Ejemplo n.º 12
0
    def _expected_move(self):
        """
        Put the expected move logic in separate (fixture) if it gets used
        multiple times
        """
        uv = r_theta_to_uv_wind(self.time_val['value'])
        exp = np.zeros((self.sc.num_released, 3))
        exp[:, 0] = self.sc['windages'] * uv[0, 0] * self.time_step
        exp[:, 1] = self.sc['windages'] * uv[0, 1] * self.time_step

        xform = FlatEarthProjection.meters_to_lonlat(exp, self.sc['positions'])
        return xform
Ejemplo n.º 13
0
    def get_delta_RK2(self, sc, time_step, model_time, pos, vel_field):
        dt = timedelta(seconds=time_step)
        dt_s = dt.seconds
        t = model_time

        v0 = vel_field.at(pos, t)
        d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s, pos)
        p1 = pos.copy()
        p1 += d0

        v1 = vel_field.at(p1, t + dt)

        return dt_s / 2 * (v0 + v1)
Ejemplo n.º 14
0
    def get_delta_RK2(self, sc, time_step, model_time, pos, vel_field):
        dt = timedelta(seconds=time_step)
        dt_s = dt.seconds
        t = model_time

        v0 = vel_field.at(pos, t)
        d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s, pos)
        p1 = pos.copy()
        p1 += d0

        v1 = vel_field.at(p1, t + dt)

        return dt_s / 2 * (v0 + v1)
Ejemplo n.º 15
0
    def get_delta_RK4(self, sc, time_step, model_time, pos, vel_field):
        dt = timedelta(seconds=time_step)
        dt_s = dt.seconds
        t = model_time

        v0 = vel_field.at(pos, t)
        d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s / 2, pos)
        p1 = pos.copy()
        p1 += d0

        v1 = vel_field.at(p1, t + dt / 2)
        d1 = FlatEarthProjection.meters_to_lonlat(v1 * dt_s / 2, pos)
        p2 = pos.copy()
        p2 += d1

        v2 = vel_field.at(p2, t + dt / 2)
        d2 = FlatEarthProjection.meters_to_lonlat(v2 * dt_s, pos)
        p3 = pos.copy()
        p3 += d2

        v3 = vel_field.at(p3, t + dt)

        return dt_s / 6 * (v0 + 2 * v1 + 2 * v2 + v3)
Ejemplo n.º 16
0
    def get_delta_RK4(self, sc, time_step, model_time, pos, vel_field):
        dt = timedelta(seconds=time_step)
        dt_s = dt.seconds
        t = model_time

        v0 = vel_field.at(pos, t)
        d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s / 2, pos)
        p1 = pos.copy()
        p1 += d0

        v1 = vel_field.at(p1, t + dt / 2)
        d1 = FlatEarthProjection.meters_to_lonlat(v1 * dt_s / 2, pos)
        p2 = pos.copy()
        p2 += d1

        v2 = vel_field.at(p2, t + dt / 2)
        d2 = FlatEarthProjection.meters_to_lonlat(v2 * dt_s, pos)
        p3 = pos.copy()
        p3 += d2

        v3 = vel_field.at(p3, t + dt)

        return dt_s / 6 * (v0 + 2 * v1 + 2 * v2 + v3)
Ejemplo n.º 17
0
def test_basic_move():

    # initilizes to long, lat, z = 0.0, 0.0, 0.0

    sp = sample_sc_release(num_elements=5)
    mover = simple_mover.SimpleMover(velocity=(1.0, 10.0, 0.0))

    delta = mover.get_move(sp, time_step=100.0, model_time=None)
    print delta

    # expected = np.zeros_like(delta)

    expected = proj.meters_to_lonlat((100.0, 1000.0, 0.0), (0.0, 0.0, 0.0))

    assert np.alltrue(delta == expected)
Ejemplo n.º 18
0
def test_basic_move():

    # initilizes to long, lat, z = 0.0, 0.0, 0.0

    sp = sample_sc_release(num_elements=5)
    mover = simple_mover.SimpleMover(velocity=(1.0, 10.0, 0.0))

    delta = mover.get_move(sp, time_step=100.0, model_time=None)
    print delta

    # expected = np.zeros_like(delta)

    expected = proj.meters_to_lonlat((100.0, 1000.0, 0.0), (0.0, 0.0,
            0.0))

    assert np.alltrue(delta == expected)
Ejemplo n.º 19
0
    def get_move(self, sc, time_step, model_time_datetime, num_method=None):
        """
        Compute the move in (long,lat,z) space. It returns the delta move
        for each element of the spill as a numpy array of size
        (number_elements X 3) and dtype = gnome.basic_types.world_point_type

        Base class returns an array of numpy.nan for delta to indicate the
        get_move is not implemented yet.

        Each class derived from Mover object must implement it's own get_move

        :param sc: an instance of gnome.spill_container.SpillContainer class
        :param time_step: time step in seconds
        :param model_time_datetime: current model time as datetime object

        All movers must implement get_move() since that's what the model calls
        """
        positions = sc['positions']

        if self.active and len(positions) > 0:
            status = sc['status_codes'] != oil_status.in_water
            pos = positions[:]

            res = self.delta_method(num_method)(sc, time_step,
                                                model_time_datetime, pos,
                                                self.current)

            if res.shape[1] == 2:
                deltas = np.zeros_like(positions)
                deltas[:, 0:2] = res
            else:
                deltas = res

            deltas *= self.scale_value

            deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions)
            deltas[status] = (0, 0, 0)
        else:
            deltas = np.zeros_like(positions)

        return deltas
Ejemplo n.º 20
0
def test_variance1(start_loc, time_step):
    """
    After a few timesteps the variance of the particle positions should be
    similar to the computed value: var = Dt
    """

    num_le = 1000
    start_time = datetime.datetime(2012, 11, 10, 0)
    sc = sample_sc_release(num_le, start_loc, start_time)
    D = 100000
    num_steps = 10

    rand = RandomMover(diffusion_coef=D)

    model_time = start_time
    for i in range(num_steps):
        model_time += datetime.timedelta(seconds=time_step)
        sc.release_elements(time_step, model_time)
        rand.prepare_for_model_step(sc, time_step, model_time)
        delta = rand.get_move(sc, time_step, model_time)

        # print "delta:", delta

        sc['positions'] += delta

        # print sc['positions']

    # compute the variances:
    # convert to meters

    pos = FlatEarthProjection.lonlat_to_meters(sc['positions'],
            start_loc)
    var = np.var(pos, axis=0)

    # D converted to meters^s/s

    expected = 2.0 * (D * 1e-4) * num_steps * time_step

    assert np.allclose(var, (expected, expected, 0.), rtol=0.1)
Ejemplo n.º 21
0
def test_variance1(start_loc, time_step):
    """
    After a few timesteps the variance of the particle positions should be
    similar to the computed value: var = Dt
    """

    num_le = 1000
    start_time = datetime.datetime(2012, 11, 10, 0)
    sc = sample_sc_release(num_le, start_loc, start_time)
    D = 100000
    num_steps = 10

    rand = RandomMover(diffusion_coef=D)

    model_time = start_time
    for i in range(num_steps):
        model_time += datetime.timedelta(seconds=time_step)
        sc.release_elements(time_step, model_time)
        rand.prepare_for_model_step(sc, time_step, model_time)
        delta = rand.get_move(sc, time_step, model_time)

        # print "delta:", delta

        sc['positions'] += delta

        # print sc['positions']

    # compute the variances:
    # convert to meters

    pos = FlatEarthProjection.lonlat_to_meters(sc['positions'], start_loc)
    var = np.var(pos, axis=0)

    # D converted to meters^s/s

    expected = 2.0 * (D * 1e-4) * num_steps * time_step

    assert np.allclose(var, (expected, expected, 0.), rtol=0.1)
Ejemplo n.º 22
0
    def get_move(self, sc, time_step, model_time_datetime, num_method=None):
        """
        Compute the move in (long,lat,z) space. It returns the delta move
        for each element of the spill as a numpy array of size
        (number_elements X 3) and dtype = gnome.basic_types.world_point_type

        Base class returns an array of numpy.nan for delta to indicate the
        get_move is not implemented yet.

        Each class derived from Mover object must implement it's own get_move

        :param sc: an instance of gnome.spill_container.SpillContainer class
        :param time_step: time step in seconds
        :param model_time_datetime: current model time as datetime object

        All movers must implement get_move() since that's what the model calls
        """
        method = None
        if num_method is None:
            method = self.num_methods[self.default_num_method]
        else:
            method = self.num_method[num_method]

        status = sc["status_codes"] != oil_status.in_water
        positions = sc["positions"]
        pos = positions[:]

        res = method(sc, time_step, model_time_datetime, pos, self.current)
        if res.shape[1] == 2:
            deltas = np.zeros_like(positions)
            deltas[:, 0:2] = res
        else:
            deltas = res

        deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions)
        deltas[status] = (0, 0, 0)
        return deltas
Ejemplo n.º 23
0
class SimpleMover(Mover):
    """
    simple_mover

    a really simple mover -- moves all LEs a constant speed and direction

    (not all that different than a constant wind mover, now that I
     think about it)
    """

    _schema = SimpleMoverSchema

    def __init__(self, velocity, uncertainty_scale=0.5, **kwargs):
        """
        simple_mover (velocity)

        create a simple_mover instance

        :param velocity: a (u, v, w) triple -- in meters per second

        :param uncertainty_scale=0.5: the scale of the uncertainty of the velocity

        Remaining kwargs are passed onto Mover's __init__ using super.
        See Mover documentation for remaining valid kwargs.
        """

        # use this, to be compatible with whatever we are using for location
        self.velocity = np.asarray(velocity, dtype=mover_type).reshape((3, ))

        self.uncertainty_scale = uncertainty_scale
        super(SimpleMover, self).__init__(**kwargs)

    def __repr__(self):
        return "SimpleMover(velocity={!r}, uncertainty_scale={!r})".format(
            self.velocity, self.uncertainty_scale)

    def get_move(self, spill, time_step, model_time):
        """
        moves the particles defined in the spill object

        :param spill: spill is an instance of the gnome.spill.Spill class
        :param time_step: time_step in seconds
        :param model_time: current model time as a datetime object
        In this case, it uses the:
            positions
            status_code
        data arrays.

        :returns delta: Nx3 numpy array of movement
                        -- in (long, lat, meters) units
        """

        # Get the data:

        try:
            positions = spill['positions']
            status_codes = spill['status_codes']
        except KeyError, err:
            raise ValueError("The spill doesn't have the required "
                             "data arrays\n{}".format(err))

        # which ones should we move?

        in_water_mask = status_codes == oil_status.in_water

        # compute the move

        delta = np.zeros_like(positions)

        if self.active and self.on:
            delta[in_water_mask] = self.velocity * time_step

            # add some random stuff if uncertainty is on

            if spill.uncertain:
                num = sum(in_water_mask)
                scale = self.uncertainty_scale * self.velocity \
                    * time_step
                delta[in_water_mask,
                      0] += random.uniform(-scale[0], scale[0], num)
                delta[in_water_mask,
                      1] += random.uniform(-scale[1], scale[1], num)
                delta[in_water_mask,
                      2] += random.uniform(-scale[2], scale[2], num)

            # scale for projection

            # just the lat-lon...
            delta = proj.meters_to_lonlat(delta, positions)

        return delta
Ejemplo n.º 24
0
    def set_newparticle_values(self, num_new_particles, current_time,
                               time_step, data_arrays):
        """
        SpillContainer will release elements and initialize all data_arrays
        to default initial value. The SpillContainer gets passed as input and
        the data_arrays for 'position' get initialized correctly by the release
        object: self.release.set_newparticle_positions()

        If a Spill Amount is given, the Spill object also sets the 'mass' data
        array; else 'mass' array remains '0'

        :param int num_new_particles: number of new particles that were added.
            Always greater than 0
        :param current_time: current time
        :type current_time: datetime.datetime
        :param time_step: the time step, sometimes used to decide how many
            should get released.
        :type time_step: integer seconds
        :param data_arrays: dict of data_arrays provided by the SpillContainer.
            Look for 'positions' array in the dict and update positions for
            latest num_new_particles that are released
        :type data_arrays: dict containing numpy arrays for values

        Also, the set_newparticle_values() method for all element_type gets
        called so each element_type sets the values for its own data correctly
        """
        mass_fluxes = [tam_drop.mass_flux for tam_drop in self.droplets]
        delta_masses, proportions, total_mass = self._get_mass_distribution(mass_fluxes, time_step)

        # set up LE distribution, the number of particles in each 'release point'
        LE_distribution = [int(num_new_particles * p) for p in proportions]
        diff = num_new_particles - sum(LE_distribution)
        for i in range(0, diff):
            LE_distribution[i % len(LE_distribution)] += 1

        # compute release point location for each droplet
        positions = [self.start_position + FlatEarthProjection.meters_to_lonlat(d.position, self.start_position) for d in self.droplets]
        for p in positions:
            p[0][2] -= self.start_position[2]

        # for each release location, set the position and mass of the elements released at that location
        total_rel = 0
        for mass_dist, n_LEs, pos, droplet in zip(delta_masses, LE_distribution, positions, self.droplets):
            start_idx = -num_new_particles + total_rel
            if start_idx == 0:
                break
            end_idx = start_idx + n_LEs
            if end_idx == 0:
                end_idx = None
#             print '{0} to {1}'.format(start_idx, end_idx)
            if start_idx == end_idx:
                continue

            data_arrays['positions'][start_idx:end_idx] = pos
            data_arrays['mass'][start_idx:end_idx] = mass_dist / n_LEs
            data_arrays['init_mass'][start_idx:end_idx] = mass_dist / n_LEs
            data_arrays['density'][start_idx:end_idx] = droplet.density
            data_arrays['droplet_diameter'][start_idx:end_idx] = np.random.normal(droplet.radius * 2, droplet.radius * 0.15, (n_LEs))
            v = data_arrays['rise_vel'][start_idx:end_idx]
            rise_velocity_from_drop_size(v,
                                         data_arrays['density'][start_idx:end_idx],
                                         data_arrays['droplet_diameter'][start_idx:end_idx],
                                         0.0000013, 1020)
            data_arrays['rise_vel'][start_idx:end_idx] = v
            total_rel += n_LEs

        self.num_released += num_new_particles
        self.amount_released += total_mass
Ejemplo n.º 25
0
class SimpleMover(Mover, serializable.Serializable):
    """
    simple_mover
    
    a really simple mover -- moves all LEs a constant speed and direction
    
    (not all that different than a constant wind mover, now that I think about it)    
    """

    _state = copy.deepcopy(Mover._state)
    _state.add(update=['uncertainty_scale', 'velocity'],
               save=['uncertainty_scale', 'velocity'])

    def __init__(self, velocity, uncertainty_scale=0.5, **kwargs):
        """
        simple_mover (velocity)

        create a simple_mover instance

        :param velocity: a (u, v, w) triple -- in meters per second
        
        Remaining kwargs are passed onto Mover's __init__ using super. 
        See Mover documentation for remaining valid kwargs.
        """

        self.velocity = np.asarray(
            velocity, dtype=basic_types.mover_type
        ).reshape(
            (3, )
        )  # use this, to be compatible with whatever we are using for location
        self.uncertainty_scale = uncertainty_scale
        super(SimpleMover, self).__init__(**kwargs)

    def __repr__(self):
        return 'SimpleMover(<%s>)' % self.id

    def velocity_to_dict(self):
        """
        convert velocity back into a tuple
        """

        return tuple(self.velocity.tolist())

    def get_move(
        self,
        spill,
        time_step,
        model_time,
    ):
        """
        moves the particles defined in the spill object
        
        :param spill: spill is an instance of the gnome.spill.Spill class
        :param time_step: time_step in seconds
        :param model_time: current model time as a datetime object
        In this case, it uses the:
            positions
            status_code
        data arrays.
        
        :returns delta: Nx3 numpy array of movement -- in (long, lat, meters) units
        
        """

        # Get the data:

        try:
            positions = spill['positions']
            status_codes = spill['status_codes']
        except KeyError, err:
            raise ValueError(
                'The spill does not have the required data arrays\n' +
                err.message)

        # which ones should we move?

        in_water_mask = status_codes == basic_types.oil_status.in_water

        # compute the move

        delta = np.zeros_like(positions)

        if self.active and self.on:
            delta[in_water_mask] = self.velocity * time_step

            # add some random stuff if uncertainty is on

            if spill.uncertain:
                num = sum(in_water_mask)
                scale = self.uncertainty_scale * self.velocity \
                    * time_step
                delta[in_water_mask,
                      0] += random.uniform(-scale[0], scale[0], num)
                delta[in_water_mask,
                      1] += random.uniform(-scale[1], scale[1], num)
                delta[in_water_mask,
                      2] += random.uniform(-scale[2], scale[2], num)

            # scale for projection

            delta = proj.meters_to_lonlat(delta,
                                          positions)  # just the lat-lon...

        return delta