def test_reverse_up_left(self): s = Shoreline(type='reverse') starting = Location4D(latitude=39.05, longitude=-75.34, depth=0) ending = Location4D(latitude=38.96, longitude=-75.315, depth=0) difference = AsaGreatCircle.great_distance(start_point=starting, end_point=ending) angle = AsaMath.azimuth_to_math_angle(azimuth=difference['azimuth']) distance = difference['distance'] intersection = s.intersect(start_point=starting.point, end_point=ending.point) int4d = Location4D(point=intersection['point']) final_point = s.react(start_point=starting, hit_point=int4d, end_point=ending, feature=intersection['feature'], distance=distance, angle=angle, azimuth=difference['azimuth'], reverse_azimuth=difference['reverse_azimuth']) # Resulting latitude should be between the startpoint and the intersection point assert final_point.latitude > int4d.latitude assert final_point.latitude < starting.latitude # Resulting longitude should be between the startpoint and the intersection point assert final_point.longitude < int4d.longitude assert final_point.longitude > starting.longitude
def test_reverse_half_distance_until_in_water(self): s = Shoreline(type='reverse') starting = Location4D(latitude=39.05, longitude=-75.34, depth=0) ending = Location4D(latitude=38.96, longitude=-75.315, depth=0) difference = AsaGreatCircle.great_distance(start_point=starting, end_point=ending) angle = AsaMath.azimuth_to_math_angle(azimuth=difference['azimuth']) distance = difference['distance'] intersection = s.intersect(start_point=starting.point, end_point=ending.point) int4d = Location4D(point=intersection['point']) final_point = s.react(start_point=starting, hit_point=int4d, end_point=ending, feature=intersection['feature'], distance=distance, angle=angle, azimuth=difference['azimuth'], reverse_azimuth=difference['reverse_azimuth'], reverse_distance=40000) # Should be in water assert s.intersect(start_point=final_point.point, end_point=final_point.point) is None
def test_reverse_distance_traveled(self): s = Shoreline(type='reverse') starting = Location4D(latitude=39.05, longitude=-75.34, depth=0) ending = Location4D(latitude=38.96, longitude=-75.315, depth=0) difference = AsaGreatCircle.great_distance(start_point=starting, end_point=ending) angle = AsaMath.azimuth_to_math_angle(azimuth=difference['azimuth']) distance = difference['distance'] intersection = s.intersect(start_point=starting.point, end_point=ending.point) int4d = Location4D(point=intersection['point']) final_point = s.react(start_point=starting, hit_point=int4d, end_point=ending, feature=intersection['feature'], distance=distance, angle=angle, azimuth=difference['azimuth'], reverse_azimuth=difference['reverse_azimuth'], reverse_distance=0.000001) # Resulting point should be VERY close to the hit point. assert abs(int4d.latitude - final_point.latitude) < 0.005 assert abs(int4d.longitude - final_point.longitude) < 0.005
def test_reverse_left(self): s = Shoreline(type='reverse') starting = Location4D(latitude=39.1, longitude=-74.91, depth=0) ending = Location4D(latitude=39.1, longitude=-74.85, depth=0) difference = AsaGreatCircle.great_distance(start_point=starting, end_point=ending) angle = AsaMath.azimuth_to_math_angle(azimuth=difference['azimuth']) distance = difference['distance'] intersection = s.intersect(start_point=starting.point, end_point=ending.point) int4d = Location4D(point=intersection['point']) final_point = s.react(start_point=starting, hit_point=int4d, end_point=ending, feature=intersection['feature'], distance=distance, angle=angle, azimuth=difference['azimuth'], reverse_azimuth=difference['reverse_azimuth']) # Since we are on a stright horizonal line, the latitude will change only slightly assert abs(final_point.latitude - starting.latitude) < 0.005 # Resulting longitude should be between the startpoint and the intersection point assert final_point.longitude < int4d.longitude assert final_point.longitude > starting.longitude
def test_reverse_12_times_then_start_point(self): s = Shoreline(type='reverse') starting = Location4D(latitude=39.05, longitude=-75.34, depth=0) ending = Location4D(latitude=38.96, longitude=-75.315, depth=0) difference = AsaGreatCircle.great_distance(start_point=starting, end_point=ending) angle = AsaMath.azimuth_to_math_angle(azimuth=difference['azimuth']) distance = difference['distance'] intersection = s.intersect(start_point=starting.point, end_point=ending.point) int4d = Location4D(point=intersection['point']) final_point = s.react(start_point=starting, hit_point=int4d, end_point=ending, feature=intersection['feature'], distance=distance, angle=angle, azimuth=difference['azimuth'], reverse_azimuth=difference['reverse_azimuth'], reverse_distance=9999999999999999999999999999) # Should be start location assert final_point.longitude == starting.longitude assert final_point.latitude == starting.latitude assert final_point.depth == starting.depth
def test_normalization(self): p = Particle() dt = datetime(2012, 8, 15, 0, tzinfo=pytz.utc) norms = [dt] last_real_movement = Location4D(latitude=38, longitude=-76, depth=0, time=dt) p.location = Location4D(latitude=100, longitude=-100, depth=0, time=dt) p.location = Location4D(latitude=101, longitude=-101, depth=0, time=dt) p.location = last_real_movement for x in range(1, 10): norm = (dt + timedelta(hours=x)).replace(tzinfo=pytz.utc) norms.append(norm) p.location = Location4D(latitude=38 + x, longitude=-76 + x, depth=x, time=norm) locs = p.normalized_locations(norms) assert locs[0] == last_real_movement
def test_land_start_land_end_intersection(self): # Starts on land and ends on land s = Shoreline() # -75, 39.4 is on land # -75, 39.5 is on land starting = Location4D(latitude=39.4, longitude=-75, depth=0).point ending = Location4D(latitude=39.5, longitude=-75, depth=0).point self.assertRaises(Exception, s.intersect, start_point=starting, end_point=ending)
def test_land_start_water_end_intersection(self): # Starts on land and ends in the water s = Shoreline() # -75, 39.5 is on land # -75, 39 is in the middle of the Delaware Bay starting = Location4D(latitude=39.5, longitude=-75, depth=0).point ending = Location4D(latitude=39, longitude=-75, depth=0).point self.assertRaises(Exception, s.intersect, start_point=starting, end_point=ending)
def test_water_start_land_end_intersection(self): # Starts in the water and ends on land s = Shoreline() # -75, 39 is in the middle of the Delaware Bay # -75, 39.5 is on land # Intersection should be a Point starting somewhere around -75, 39.185 -> 39.195 starting = Location4D(latitude=39, longitude=-75, depth=0).point ending = Location4D(latitude=39.5, longitude=-75, depth=0).point intersection = Location4D( point=s.intersect(start_point=starting, end_point=ending)['point']) assert -75 == intersection.longitude assert intersection.latitude > 39.185 assert intersection.latitude < 39.195
def test_large_shape_intersection_speed(self): # Intersects on the west coast of NovaScotia starting = Location4D(longitude=-146.62, latitude=60.755, depth=0).point ending = Location4D(longitude=-146.60, latitude=60.74, depth=0).point shore_path = os.path.join(self.shoreline_path, "alaska", "AK_Land_Basemap.shp") s = Shoreline(file=shore_path, point=starting, spatialbuffer=0.25) st = time.time() intersection = s.intersect(start_point=starting, end_point=ending)['point'] print "Large Shoreline Intersection Time: " + str(time.time() - st)
def test_multipart_shape_intersection_speed(self): # Intersects on the west coast of NovaScotia starting = Location4D(longitude=-146.62, latitude=60.755, depth=0).point ending = Location4D(longitude=-146.60, latitude=60.74, depth=0).point shore_path = os.path.join(self.shoreline_path, "westcoast", "New_Land_Clean.shp") s = Shoreline(file=shore_path, point=starting, spatialbuffer=1) st = time.time() intersection = s.intersect(start_point=starting, end_point=ending)['point'] print "Multipart Shoreline Intersection Time: " + str(time.time() - st)
def test_moving_particle(self): for p in self.particles: for i in xrange(0, len(self.times)): try: modelTimestep = self.times[i + 1] - self.times[i] calculatedTime = self.times[i + 1] except StandardError: modelTimestep = self.times[i] - self.times[i - 1] calculatedTime = self.times[i] + modelTimestep newtime = self.start_time + timedelta(seconds=calculatedTime) p.age(seconds=modelTimestep) movement = self.transport_model.move(p, self.u[i], self.v[i], self.z[i], modelTimestep) newloc = Location4D(latitude=movement['latitude'], longitude=movement['longitude'], depth=movement['depth'], time=newtime) p.location = newloc for p in self.particles: # Particle should move every timestep assert len(p.locations) == len(self.times) + 1 # A particle should always move in this test assert len(set(p.locations)) == len(self.times) + 1 # A particle should always age assert p.get_age( units='days') == (self.times[-1] + 3600) / 60. / 60. / 24. # First point of each particle should be the starting location assert p.linestring().coords[0][0] == self.loc.longitude assert p.linestring().coords[0][1] == self.loc.latitude assert p.linestring().coords[0][2] == self.loc.depth
def setUp(self): start_lat = 38 start_lon = -76 start_depth = -5 temp_time = datetime.utcnow() self.start_time = datetime(temp_time.year, temp_time.month, temp_time.day, temp_time.hour) self.loc = Location4D(latitude=start_lat, longitude=start_lon, depth=start_depth, time=self.start_time) # Generate time,u,v,z as randoms # 48 timesteps at an hour each = 2 days of running self.times = range(0, 172800, 3600) # in seconds self.u = [] self.v = [] self.z = [] for w in xrange(0, 48): self.z.append(random.gauss(0, 0.0001)) # gaussian in m/s self.u.append(abs(AsaRandom.random())) # random function in m/s self.v.append(abs(AsaRandom.random())) # random function in m/s self.particles = [] # Create particles for i in xrange(0, 3): p = Particle() p.location = self.loc self.particles.append(p) # Create a transport instance with horiz and vert dispersions self.transport_model = Transport(horizDisp=0.05, vertDisp=0.00003)
def test_intersection_speed(self): # Intersects on the west coast of NovaScotia starting = Location4D(longitude=-66.1842219282406177, latitude=44.0141581697495852, depth=0).point ending = Location4D(longitude=-66.1555195384399326, latitude=44.0387992322117370, depth=0).point s = Shoreline(point=starting, spatialbuffer=1) st = time.time() intersection = s.intersect(start_point=starting, end_point=ending)['point'] print "Intersection Time: " + str(time.time() - st)
def test_cycle(self): t = datetime.utcnow().replace(tzinfo=pytz.utc) loc = Location4D(time=t, latitude=35, longitude=-76) c = SunCycles.cycles(loc=loc) sunrise = c[SunCycles.RISING] sunset = c[SunCycles.SETTING] d = Diel() d.min_depth = -4 d.max_depth = -10 d.pattern = 'cycles' d.cycle = 'sunrise' d.plus_or_minus = '+' d.time_delta = 4 assert d.get_time(loc4d=loc) == sunrise + timedelta(hours=4) d = Diel() d.min_depth = -4 d.max_depth = -10 d.pattern = 'cycles' d.cycle = 'sunset' d.plus_or_minus = '-' d.time_delta = 2 assert d.get_time(loc4d=loc) == sunset - timedelta(hours=2)
def setUp(self): data = open( os.path.normpath( os.path.join( os.path.dirname(__file__), "./resources/files/lifestage_single.json"))).read() self.lifestage = LifeStage(json=data) start_lat = 38 start_lon = -76 start_depth = -5 temp_time = datetime.utcnow() self.start_time = datetime(temp_time.year, temp_time.month, temp_time.day, temp_time.hour).replace(tzinfo=pytz.utc) self.loc = Location4D(latitude=start_lat, longitude=start_lon, depth=start_depth, time=self.start_time) self.particles = [] # Create particles for i in range(0, 3): p = LarvaParticle() p.location = self.loc self.particles.append(p) # 48 timesteps at an hour each = 2 days of running self.times = list(range(0, 172800, 3600)) # in seconds self.temps = [] self.salts = [] for w in range(0, 48): self.temps.append(random.randint(20, 40)) self.salts.append(random.randint(10, 30))
def get_active_diel(self, loc4d): active_diel = None if len(self.diel) > 0: particle_time = loc4d.time # Find the closests Diel that the current particle time is AFTER, and set it to the active_diel closest = None closest_seconds = None for ad in self.diel: # To handle thecase where a particle at 1:00am only looks at Diels for that # day and does not act upon Diel from the previous day at, say 11pm, check # both today's Diel times and the particles current days Diel times. yesterday = Location4D(location=loc4d) yesterday.time = yesterday.time - timedelta(days=1) times = [ ad.get_time(loc4d=loc4d), ad.get_time(loc4d=yesterday) ] for t in times: if t <= particle_time: seconds = (particle_time - t).total_seconds() if closest is None or seconds < closest_seconds: closest = ad closest_seconds = seconds del yesterday active_diel = closest return active_diel
def test_from_dict(self): data = open( os.path.normpath( os.path.join( os.path.dirname(__file__), "./resources/files/lifestage_single.json"))).read() l = LifeStage(data=json.loads(data)) assert l.name == 'third' assert l.duration == 3 assert l.linear_a == 0.03 assert l.linear_b == 0.2 assert len(l.taxis) == 2 assert l.taxis[1].min_value == -30.0 assert l.taxis[1].max_value == -50.0 assert len(l.diel) == 2 assert l.diel[1].min_depth == -2.0 assert l.diel[1].max_depth == -5.0 assert l.capability.vss == 5.0 assert l.capability.variance == 2.0 assert l.capability.non_swim_turning == 'random' assert l.capability.swim_turning == 'random' t = datetime.utcnow().replace(tzinfo=pytz.utc) loc = Location4D(time=t, latitude=35, longitude=-76) assert isinstance(l.diel[0].get_time(loc4d=loc), datetime)
def __reverse(self, **kwargs): """ If we hit the bathymetry, set the location to where we came from. """ start_point = kwargs.pop('start_point') return Location4D(latitude=start_point.latitude, longitude=start_point.longitude, depth=start_point.depth)
def test_water_start_water_end_jump_over_land_intersection(self): # Starts on water and ends on water, but there is land inbetween s = Shoreline() # -75, 39 is in the middle of the Delaware Bay # -74, 39 is in the Atlantic # This jumps over a peninsula. # Intersection should be the Point -74.96 -> -74.94, 39 # starting = Location4D(latitude=39, longitude=-75, depth=0).point ending = Location4D(latitude=39, longitude=-74, depth=0).point intersection = Location4D( point=s.intersect(start_point=starting, end_point=ending)['point']) assert 39 == intersection.latitude assert intersection.longitude > -74.96 assert intersection.longitude < -74.94
def nearest_time(self, time): new = self._copy() if type(time) != Location4D: time = Location4D(time=time, latitude=0, longitude=0) for var in self._current_variables: time_dimension = new.gettimevar(var) if time_dimension is not None: ind = new.get_nearest_tind(var, time) time_dimension = _sub_by_nan(time_dimension, ind) new._coordcache[var].t = time_dimension return new
def nearest_depth(self, depth): new = self._copy() if type(depth) != Location4D: depth = Location4D(depth=depth, latitude=0, longitude=0) for var in self._current_variables: depth_dimension = new.getdepthvar(var) if depth_dimension is not None: ind = new.get_nearest_zind(var, depth) depth_dimension = _sub_by_nan(depth_dimension, ind) new._coordcache[var].z = depth_dimension return new
def test_great_circle_angles(self): # One decimal degree is 111000m starting = Location4D(latitude=40.00, longitude=-76.00, depth=0) azimuth = 90 new_gc = AsaGreatCircle.great_circle(distance=111000, azimuth=azimuth, start_point=starting) new_pt = Location4D(latitude=new_gc['latitude'], longitude=new_gc['longitude']) # We should have gone to the right assert new_pt.longitude > starting.longitude + 0.9 azimuth = 270 new_gc = AsaGreatCircle.great_circle(distance=111000, azimuth=azimuth, start_point=starting) new_pt = Location4D(latitude=new_gc['latitude'], longitude=new_gc['longitude']) # We should have gone to the left assert new_pt.longitude < starting.longitude - 0.9 azimuth = 180 new_gc = AsaGreatCircle.great_circle(distance=111000, azimuth=azimuth, start_point=starting) new_pt = Location4D(latitude=new_gc['latitude'], longitude=new_gc['longitude']) # We should have gone down assert new_pt.latitude < starting.latitude - 0.9 azimuth = 0 new_gc = AsaGreatCircle.great_circle(distance=111000, azimuth=azimuth, start_point=starting) new_pt = Location4D(latitude=new_gc['latitude'], longitude=new_gc['longitude']) # We should have gone up assert new_pt.latitude > starting.latitude + 0.9 azimuth = 315 new_gc = AsaGreatCircle.great_circle(distance=111000, azimuth=azimuth, start_point=starting) new_pt = Location4D(latitude=new_gc['latitude'], longitude=new_gc['longitude']) # We should have gone up and to the left assert new_pt.latitude > starting.latitude + 0.45 assert new_pt.longitude < starting.longitude - 0.45
def test_no_diel(self): data = json.loads( open( os.path.normpath( os.path.join( os.path.dirname(__file__), "./resources/files/lifestage_single.json"))).read()) data['diel'] = [] self.lifestage = LifeStage(data=data) for p in self.particles: for i in range(0, len(self.times)): try: modelTimestep = self.times[i + 1] - self.times[i] calculatedTime = self.times[i + 1] except Exception: modelTimestep = self.times[i] - self.times[i - 1] calculatedTime = self.times[i] + modelTimestep newtime = self.start_time + timedelta(seconds=calculatedTime) p.age(seconds=modelTimestep) movement = self.lifestage.move(p, 0, 0, 0, modelTimestep, temperature=self.temps[i], salinity=self.salts[i]) newloc = Location4D(latitude=movement['latitude'], longitude=movement['longitude'], depth=movement['depth'], time=newtime) p.location = newloc for p in self.particles: # Particle should move every timestep assert len(p.locations) == len(self.times) + 1 # A particle should always move in this test assert len(set(p.locations)) == len(self.times) + 1 # A particle should always age assert p.get_age( units='days') == (self.times[-1] + 3600) / 60. / 60. / 24. # First point of each particle should be the starting location assert p.linestring().coords[0][0] == self.loc.longitude assert p.linestring().coords[0][1] == self.loc.latitude assert p.linestring().coords[0][2] == self.loc.depth # Lifestages currently influence the Z direction, so a particle should not # move horizontally. assert p.linestring().coords[-1][0] == self.loc.longitude assert p.linestring().coords[-1][1] == self.loc.latitude
def __hover(self, **kwargs): """ This hovers the particle 1m above the bathymetry WHERE IT WOULD HAVE ENDED UP. This is WRONG and we need to compute the location that it actually hit the bathymetry and hover 1m above THAT. """ end_point = kwargs.pop('end_point') # The location argument here should be the point that intersected the bathymetry, # not the end_point that is "through" the bathymetry. depth = self.get_depth(location=end_point) return Location4D(latitude=end_point.latitude, longitude=end_point.longitude, depth=(depth + 1.))
def attempt(self, particle, depth): # We may want to have settlement affect the u/v/w in the future u = 0 v = 0 w = 0 # If the particle is settled, don't move it anywhere if particle.settled: return (0, 0, 0) # A particle is negative down from the sea surface, so "-3" is 3 meters below the surface. # We are assuming here that the bathymetry is also negative down. if self.type.lower() == "benthic": # Is the sea floor within the upper and lower bounds? if self.upper >= depth >= self.lower: # Move the particle to the sea floor. # TODO: Should the particle just swim downwards? newloc = Location4D(location=particle.location) newloc.depth = depth particle.location = newloc particle.settle() logger.info("Particle %d settled in %s mode" % (particle.uid, self.type)) elif self.type.lower() == "pelagic": # Are we are in enough water to settle # Ignore this bathymetry test since we would need a high resolution # dataset for this to work. #if self.upper >= depth: # Is the particle within the range? if self.upper >= particle.location.depth >= self.lower: # Just settle the particle particle.settle() logger.info("Particle %d settled in %s mode" % (particle.uid, self.type)) else: logger.debug( "Particle did NOT settle. Depth conditions not met. Upper limit: %d - Lower limit: %d - Particle: %d" % (self.upper, self.lower, particle.location.depth)) #else: # logger.info("Particle did NOT settle. Water not deep enough. Upper limit: %d - Bathymetry: %d" % (self.upper, depth)) else: logger.warn( "Settlement type %s not recognized, not trying to settle Particle %d." % (self.type, particle.uid)) return (u, v, w)
def near_xy(self, **kwargs): """ TODO: Implement ncell near_xy """ point = kwargs.get("point", None) if point == None: lat = kwargs.get("lat", None) lon = kwargs.get("lon", None) point = Location4D(latitude=lat, longitude=lon) num = kwargs.get("num", 1) ncell = kwargs.get("ncell", False) if ncell: if num > 1: pass else: distance = AsaGreatCircle.great_distance( start_lats=self._yarray, start_lons=self._xarray, end_lats=point.latitude, end_lons=point.longitude)["distance"] inds = np.where(distance == np.nanmin(distance)) xinds, yinds = inds, inds else: if self._ndim == 2: distance = AsaGreatCircle.great_distance( start_lats=self._yarray, start_lons=self._xarray, end_lats=point.latitude, end_lons=point.longitude)["distance"] yinds, xinds = np.where(distance == np.nanmin(distance)) else: #if self._xmesh == None and self._ymesh == None: # self._xmesh, self._ymesh = np.meshgrid(self._xarray, self._yarray) if num > 1: minlat = np.abs(self._yarray - point.latitude) minlon = np.abs(self._xarray - point.longitude) lat_cutoff = np.sort(minlat)[num - 1] lon_cutoff = np.sort(minlon)[num - 1] elif num == 1: lat_cutoff = np.nanmin( np.abs(self._yarray - point.latitude)) lon_cutoff = np.nanmin( np.abs(self._xarray - point.longitude)) yinds = np.where( np.abs(self._yarray - point.latitude) <= lat_cutoff) xinds = np.where( np.abs(self._xarray - point.longitude) <= lon_cutoff) return yinds, xinds
def __bounce(self, **kwargs): """ Bounce off of the shoreline. NOTE: This does not work, but left here for future implementation feature = Linestring of two points, being the line segment the particle hit. angle = decimal degrees from 0 (x-axis), couter-clockwise (math style) """ start_point = kwargs.pop('start_point') hit_point = kwargs.pop('hit_point') end_point = kwargs.pop('end_point') feature = kwargs.pop('feature') distance = kwargs.pop('distance') angle = kwargs.pop('angle') # Figure out the angle of the shoreline here (beta) points_in_shore = map(lambda x: Point(x), list(feature.coords)) points_in_shore = sorted(points_in_shore, key=lambda x: x.x) # The point on the left (least longitude is always the first Point) first_shore = points_in_shore[0] last_shore = points_in_shore[-1] shoreline_x = abs(abs(first_shore.x) - abs(last_shore.x)) shoreline_y = abs(abs(first_shore.y) - abs(last_shore.y)) beta = math.degrees(math.atan(shoreline_x / shoreline_y)) theta = 90 - angle - beta bounce_azimuth = AsaMath.math_angle_to_azimuth(angle=2 * theta + angle) print "Beta: " + str(beta) print "Incoming Angle: " + str(angle) print "ShorelineAngle: " + str(theta + angle) print "Bounce Azimuth: " + str(bounce_azimuth) print "Bounce Angle: " + str( AsaMath.azimuth_to_math_angle(azimuth=bounce_azimuth)) after_distance = distance - AsaGreatCircle.great_distance( start_point=start_point, end_point=hit_point)['distance'] new_point = AsaGreatCircle.great_circle(distance=after_distance, azimuth=bounce_azimuth, start_point=hit_point) return Location4D(latitude=new_point['latitude'], longitude=new_point['longitude'], depth=start_point.depth)
def test_classmethod_with_location4d(self): loc = Location4D(time=self.dt, latitude=self.lat, longitude=self.lon) d = SunCycles.cycles(loc=loc) zrise = d[SunCycles.RISING].astimezone(timezone('US/Eastern')) assert zrise.year == 2012 assert zrise.month == 8 assert zrise.day == 6 assert zrise.hour == 5 assert zrise.minute == 46 zset = d[SunCycles.SETTING].astimezone(timezone('US/Eastern')) assert zset.year == 2012 assert zset.month == 8 assert zset.day == 6 assert zset.hour == 19 assert zset.minute == 57
def test_moving_particle_with_lifestage(self): for p in self.particles: for i in range(0, len(self.times)): try: modelTimestep = self.times[i + 1] - self.times[i] calculatedTime = self.times[i + 1] except Exception: modelTimestep = self.times[i] - self.times[i - 1] calculatedTime = self.times[i] + modelTimestep newtime = self.start_time + timedelta(seconds=calculatedTime) p.age(seconds=modelTimestep) movement = self.lifestage.move(p, 0, 0, 0, modelTimestep, temperature=self.temps[i], salinity=self.salts[i]) newloc = Location4D(latitude=movement['latitude'], longitude=movement['longitude'], depth=movement['depth'], time=newtime) p.location = newloc for p in self.particles: # Particle should move every timestep assert len(p.locations) == len(self.times) + 1 # A particle should always move in this test assert len(set(p.locations)) == len(self.times) + 1 # A particle should always age assert p.get_age( units='days') == (self.times[-1] + 3600) / 60. / 60. / 24. # First point of each particle should be the starting location assert p.linestring().coords[0][0] == self.loc.longitude assert p.linestring().coords[0][1] == self.loc.latitude assert p.linestring().coords[0][2] == self.loc.depth # Lifestages currently influence the Z direction, so a particle should not # move horizontally. assert p.linestring().coords[-1][0] == self.loc.longitude assert p.linestring().coords[-1][1] == self.loc.latitude