Commit f8ba491b authored by Domanski, Marian M.'s avatar Domanski, Marian M.
Browse files

Merge branch 'master' into 'master'

Master

See merge request !42
parents 438e2189 73dacb6f
import xml.etree.ElementTree as ElementTree
from matplotlib.cm import jet as jet_cm
from matplotlib.cm import inferno as depth_frac_cm
import numpy as np
import simplekml
......@@ -77,8 +78,8 @@ def great_circle_dist(coordinates):
d_latt = end_latt - start_latt
d_long = rads[1:, LONG_COLUMN] - rads[:-1, LONG_COLUMN]
a = np.sin(d_latt/2)**2 + np.cos(start_latt) * \
np.cos(end_latt) * np.sin(d_long/2)**2
a = np.sin(d_latt / 2)**2 + np.cos(start_latt) * \
np.cos(end_latt) * np.sin(d_long / 2)**2
c = 2 * np.arcsin(np.sqrt(a))
dist = 6371e3 * c
......@@ -87,30 +88,22 @@ def great_circle_dist(coordinates):
return cum_dist
class FluEggKML:
"""Manages KML file output for FluEgg.
class CenterLine:
"""Centerline coordinate transformations
Parameters
----------
centerline_kml_path : str
Path to stream centerline KML. The centerline KML must have uniform
spacing between the points.
spawing_location : float
Streamwise distance downstream of spawning location.
Path to stream centerline KML.
Notes
-----
Particle streamwise distances are mapped to geographic coordinates under
the assumption the points in the centerline KML are spaced equally along
the streamline.
The first coordinate in the KML centerline is assumed to be the upstream-
most point. The streamwise distance at this point is 0.
"""
def __init__(self, centerline_kml_path, spawning_location):
"""see help(self) for initialization details"""
def __init__(self, centerline_kml_path):
# coordinates and stream distances corresponding to the centerline
# points
......@@ -121,6 +114,52 @@ class FluEggKML:
# dist, lat, lon
self._centerline_coords = np.hstack((dist[:, np.newaxis], coordinates))
def geo_coordinates(self, stream_distance):
"""Geographic coordinates from stream distance
Parameters
----------
stream_distance : float, numpy.ndarray
Returns
-------
tuple
Latitude, longitude
"""
latitude = np.interp(stream_distance,
self._centerline_coords[:, 0],
self._centerline_coords[:, 1])
longitude = np.interp(stream_distance,
self._centerline_coords[:, 0],
self._centerline_coords[:, 2])
return latitude, longitude
class FluEggKML:
"""Manages KML file output for FluEgg.
Parameters
----------
centerline_kml_path : str
Path to stream centerline KML.
spawing_location : float
Streamwise distance downstream of spawning location.
Notes
-----
The first coordinate in the KML centerline is assumed to be the upstream-
most point. The streamwise distance at this point is 0.
"""
def __init__(self, centerline_kml_path, spawning_location):
"""see help(self) for initialization details"""
self._center_line = CenterLine(centerline_kml_path)
self._spawning_location = spawning_location
def _add_spawning_location(self, kml):
......@@ -138,7 +177,8 @@ class FluEggKML:
spawning_style.iconstyle.color = 'ffffff00'
spawning_style.iconstyle.scale = 1.2
spawning_coords = self._interpolate_points(self._spawning_location)
spawning_coords = self._center_line.geo_coordinates(
self._spawning_location)
spawning_location = kml.newpoint(coords=[spawning_coords])
spawning_location.style = spawning_style
......@@ -186,27 +226,15 @@ class FluEggKML:
return style
def _interpolate_points(self, point_dist):
"""
Parameters
----------
point_dist : float, numpy.ndarray
Returns
-------
latitude, longitude : float, numpy.ndarray
@staticmethod
def _rgba_to_hex(cm, x):
"""
latitude = np.interp(point_dist,
self._centerline_coords[:, 0],
self._centerline_coords[:, 1])
longitude = np.interp(point_dist,
self._centerline_coords[:, 0],
self._centerline_coords[:, 2])
rgba = jet_cm(x)
red_value = int(rgba[0] * 255)
green_value = int(rgba[1] * 255)
blue_value = int(rgba[2] * 255)
return latitude, longitude
return red_value, green_value, blue_value
def kml_particle_locations(self, point_dist, depth_fraction):
"""KML text containing georeferenced points
......@@ -217,25 +245,18 @@ class FluEggKML:
Stream distance of points (distance downstream).
depth_fraction : numpy.ndarray
Depth fraction of points. Depth fraction is the fractional height
above the bed.
Returns
-------
str
Notes
-----
Particles with depth fractions greater than or equal to 0.05 are
shown as suspended particles.
See also
--------
write_locations : Write points to a KML file
"""
# eggs greater than suspended_depth_fraction are shown as suspended
suspended_depth_fraction = 0.05
if point_dist.ndim != 1:
raise ValueError("point_dist must be a one-dimensional array")
......@@ -246,30 +267,20 @@ class FluEggKML:
raise ValueError("point_dist and depth_fraction must have " +
"the same shape")
suspended_index = suspended_depth_fraction <= depth_fraction
latitude, longitude = self._interpolate_points(point_dist)
latitude, longitude = self._center_line.geo_coordinates(point_dist)
kml = simplekml.Kml()
point_shape = 'dot'
point_scale = 0.4
suspended_color = 'y'
bottom_color = 'm'
# add suspended points
style = self._get_point_style(point_shape, suspended_color,
point_scale)
for lat, lon in zip(latitude[suspended_index],
longitude[suspended_index]):
pnt = kml.newpoint(coords=[(lat, lon)])
pnt.style = style
# add non-suspended (bottom) points
style = self._get_point_style(point_shape, bottom_color, point_scale)
for lat, lon in zip(latitude[~suspended_index],
longitude[~suspended_index]):
for i, (lat, lon) in enumerate(zip(latitude, longitude)):
x = depth_fraction[i]
pnt = kml.newpoint(coords=[(lat, lon)])
r, g, b = self._rgba_to_hex(depth_frac_cm, x)
color = simplekml.Color.rgb(r, g, b, 255)
style = simplekml.Style()
style.iconstyle.scale = 0.4
style.iconstyle.color = color
style.iconstyle.icon.href = 'http://maps.google.com/mapfiles' + \
'/kml/shapes/shaded_dot.png'
pnt.style = style
self._add_spawning_location(kml)
......@@ -320,18 +331,14 @@ class FluEggKML:
computed_quantiles = np.quantile(point_dist, quantiles,
interpolation='nearest')
la, lo = self._interpolate_points(computed_quantiles)
la, lo = self._center_line.geo_coordinates(computed_quantiles)
for i, (lat, lon) in enumerate(zip(la, lo)):
q = quantiles[i]
pnt = kml.newpoint(name=str(q), coords=[(lat, lon)])
X = 1 - abs(q - 0.5)/0.5
rgba = jet_cm(X)
red_value = int(rgba[0] * 255)
green_value = int(rgba[1] * 255)
blue_value = int(rgba[2] * 255)
color = simplekml.Color.rgb(
red_value, green_value, blue_value, 255)
X = 1 - abs(q - 0.5) / 0.5
r, g, b = self._rgba_to_hex(jet_cm, X)
color = simplekml.Color.rgb(r, g, b, 255)
style = simplekml.Style()
style.iconstyle.scale = 1.25
style.iconstyle.color = color
......@@ -357,11 +364,6 @@ class FluEggKML:
kml_path : str
Path to write KML file to.
Notes
-----
Particles with depth fractions greater than or equal to 0.05 are
shown as suspended particles.
See also
--------
kml_particle_locations : KML text containing georeferenced particle
......
......@@ -30,32 +30,55 @@ class SimulationClock:
def current_time(self):
"""Returns the current simulation time in seconds
:return: sim time (s)
:rtype: float
Returns
-------
float
Current simulation time, in seconds
"""
return self._time_array[self._current_time_index]
def current_time_index(self):
"""Returns the current simulation time index
:return: sim time index
:rtype: int
Returns
-------
int
Simulation time index
"""
return self._current_time_index
def number_of_time_steps(self):
"""Returns the total number of time steps in the simultaion
def number_of_times(self):
"""Returns the number of times in the simulation
Returns
-------
int
Number of times in the simulation
:return: num time steps
:rtype: int
"""
return self._time_array.size
def number_of_time_steps(self):
"""Returns the total number of time steps in the simulation
Returns
-------
int
Number of time steps
"""
return self._time_array.size - 1
def time_array(self):
"""Returns the array of all time steps in seconds (s)
:return: array of all time steps (s)
:rtype: np.ndarray
Returns
-------
numpy.ndarray
Array of all times, in seconds, in the simulation
"""
return self._time_array.copy()
......@@ -67,7 +90,14 @@ class SimulationClock:
"""
return self._time_step_size
def iter_time_index(self):
def iter_time_steps(self):
"""Returns iterable over time steps
Notes
-----
The first time index is 1 (not 0)
"""
return TimeStepIterable(self)
......@@ -79,7 +109,7 @@ class SimulationClock:
:return: None
"""
if (0 <= time_index) and (time_index < self.number_of_time_steps()):
if (0 <= time_index) and (time_index < self.number_of_times()):
self._current_time_index = time_index
else:
raise IndexError("Time index out of bounds")
......@@ -98,24 +128,23 @@ class TimeStepIterable:
self._simulation_clock = simulation_clock
self._number_of_time_steps = \
self._simulation_clock.number_of_time_steps()
self._current_time_step_index = 0
self._number_of_times = self._simulation_clock.number_of_times()
self._current_time_index = 1
def __iter__(self):
return self
def __next__(self):
time_step_index = self._current_time_step_index
if time_step_index == self._number_of_time_steps:
time_index = self._current_time_index
if time_index == self._number_of_times:
raise StopIteration
self._current_time_step_index += 1
self._current_time_index += 1
self._simulation_clock.set_time_index(time_step_index)
self._simulation_clock.set_time_index(time_index)
return time_step_index
return time_index
class ReverseSimulationClock(SimulationClock):
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -16,6 +16,7 @@ dev_status = 'Development Status :: 4 - Beta'
install_requirements = [
'h5py',
'matplotlib',
'numpy',
'pandas',
'scipy',
......
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Boneyard.kml</name>
<StyleMap id="m_ylw-pushpin">
<Pair>
<key>normal</key>
<styleUrl>#s_ylw-pushpin</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#s_ylw-pushpin_hl</styleUrl>
</Pair>
</StyleMap>
<Style id="s_ylw-pushpin_hl">
<IconStyle>
<scale>1.3</scale>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
</Icon>
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
</IconStyle>
<LineStyle>
<color>ffff0000</color>
<width>5</width>
</LineStyle>
</Style>
<Style id="s_ylw-pushpin">
<IconStyle>
<scale>1.1</scale>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
</Icon>
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
</IconStyle>
<LineStyle>
<color>ffff0000</color>
<width>5</width>
</LineStyle>
</Style>
<Placemark>
<name>Boneyard</name>
<styleUrl>#m_ylw-pushpin</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
-88.23028329509828,40.11095015566246,0 -88.22976523575321,40.11096928682598,0 -88.22938952057402,40.11112085856291,0 -88.22870184167087,40.11117909017261,0 -88.22850311935356,40.11124043587299,0 -88.22834852292064,40.11125581640691,0 -88.2281879696331,40.11126094825562,0 -88.22805585833179,40.11125607024084,0 -88.22789361526539,40.1112302789435,0 -88.22777340461754,40.11126098395144,0 -88.22761769073824,40.11128153565623,0 -88.22751887695942,40.11133263438763,0 -88.22737198428877,40.11137870673378,0 -88.2272446986529,40.11141451772809,0 -88.22707119657834,40.11142482056941,0 -88.22689741247967,40.11140439155387,0 -88.22679684570849,40.11135831111478,0 -88.22671664324064,40.11133273164966,0 -88.22657310935469,40.11133251358992,0 -88.22640195587468,40.11133274845086,0 -88.22620784669351,40.11133275443699,0 -88.22604726404408,40.11135836773759,0 -88.22591346242466,40.11137371209746,0 -88.22578755949562,40.11138868156644,0
</coordinates>
</LineString>
</Placemark>
</Document>
</kml>
This diff is collapsed.
......@@ -39,11 +39,11 @@ class TestAsianCarpEggs(TestCase):
nonrandom_eggs = BigheadCarpEggs(initial_position,
simulation_clock, zero_random_numbers)
diameters = np.tile(np.nan, simulation_clock.number_of_time_steps())
diameters = np.tile(np.nan, simulation_clock.number_of_times())
diameters[0] = nonrandom_eggs.diameter()
for index in simulation_clock.iter_time_index():
for index in simulation_clock.iter_time_steps():
diameters[index] = nonrandom_eggs.diameter()
self._diameters = diameters
......@@ -63,7 +63,11 @@ class TestAsianCarpEggs(TestCase):
expected_diameters = \
np.squeeze(self._simulation_results['ResultsSim']['D'][0][0])
self.assertTrue(np.allclose(expected_diameters, 1000*self._diameters))
self.assertTrue(
np.allclose(
expected_diameters,
1000 *
self._diameters))
def test_reference_density(self):
......
......@@ -16,7 +16,7 @@ from fluegg.transporter import init_transporter, LongitudinalTransporter
# add the testclasses path before importing
absolute_path, _ = os.path.split(os.path.relpath(__file__))
sys.path.append(absolute_path)
print(absolute_path)
from testclasses import ConstantVelocitySeriesOfHydraulicCells
......
import os
import unittest
import numpy as np
from fluegg.kml import FluEggKML
absolute_dir = os.path.dirname(os.path.relpath(__file__))
data_dir = os.path.join(absolute_dir, 'data')
kml_data_dir = os.path.join(data_dir, 'kml')
class TestFluEggKML(unittest.TestCase):
def test_kml_particle_locations(self):
centerline_kml_path = os.path.join(kml_data_dir, 'Boneyard.kml')
fluegg_kml = FluEggKML(centerline_kml_path, 0)
point_dist = np.linspace(0, 390)
depth_fraction = np.linspace(0, 1)
kml_path = os.path.join(kml_data_dir, 'boneyard_points.kml')
points_kml = fluegg_kml.kml_particle_locations(
point_dist, depth_fraction)
expected_kml_path = os.path.join(kml_data_dir, 'boneyard_points.kml')
with open(expected_kml_path, 'r') as f:
expected_kml = f.read()
self.assertEqual(points_kml, expected_kml)
......@@ -13,7 +13,7 @@ class TestSimulationClock(unittest.TestCase):
clock = simclock.SimulationClock(1, 5)
self.assertEqual(clock.current_time(), 0)
self.assertEqual(clock.current_time_index(), 0)
self.assertEqual(clock.number_of_time_steps(), 6)
self.assertEqual(clock.number_of_times(), 6)
self.assertTrue(np.array_equal(clock.time_array(), np.arange(0, 6, 1)))
self.assertEqual(clock.time_step_size(), 1)
......@@ -22,7 +22,7 @@ class TestSimulationClock(unittest.TestCase):
clock = simclock.SimulationClock(1, 1)
self.assertEqual(clock.current_time(), 0)
self.assertEqual(clock.current_time_index(), 0)
self.assertEqual(clock.number_of_time_steps(), 2)
self.assertEqual(clock.number_of_times(), 2)
self.assertTrue(np.array_equal(clock.time_array(), np.arange(0, 2, 1)))
self.assertEqual(clock.time_step_size(), 1)
......@@ -31,7 +31,7 @@ class TestSimulationClock(unittest.TestCase):
clock = simclock.SimulationClock(2, 6)
self.assertEqual(clock.current_time(), 0)
self.assertEqual(clock.current_time_index(), 0)
self.assertEqual(clock.number_of_time_steps(), 4)
self.assertEqual(clock.number_of_times(), 4)
self.assertTrue(np.array_equal(clock.time_array(), np.arange(0, 7, 2)))
self.assertEqual(clock.time_step_size(), 2)
......@@ -40,54 +40,55 @@ class TestSimulationClock(unittest.TestCase):
clock = simclock.SimulationClock(2, 7)
self.assertEqual(clock.current_time(), 0)
self.assertEqual(clock.current_time_index(), 0)
self.assertEqual(clock.number_of_time_steps(), 4)
self.assertEqual(clock.number_of_times(), 4)
self.assertTrue(np.array_equal(clock.time_array(), np.arange(0, 8, 2)))
self.assertEqual(clock.time_step_size(), 2)
def test_iter_time_index_standard(self):
def test_iter_time_steps_standard(self):
""" time step = 1, duration = 5"""
clock = simclock.SimulationClock(1,5)
clock_iterable = clock.iter_time_index()
clock = simclock.SimulationClock(1, 5)
clock_iterable = clock.iter_time_steps()
step_list = []
for step in clock_iterable:
step_list.append(step)
self.assertEqual(step_list, list(range(0,5+1)))
def test_iter_time_index_singular_step(self):
self.assertEqual(step_list, list(range(1, 5 + 1)))
def test_iter_time_steps_singular_step(self):
""" time step = 1, duration = 1"""
clock = simclock.SimulationClock(1,1)
clock_iterable = clock.iter_time_index()
clock = simclock.SimulationClock(1, 1)
clock_iterable = clock.iter_time_steps()
step_list = []
for step in clock_iterable:
step_list.append(step)
self.assertEqual(step_list, [0,1])
def test_iter_time_index_no_step(self):
self.assertEqual(step_list, [1])
def test_iter_time_steps_no_step(self):
""" time step = 1, duration = 0"""
clock = simclock.SimulationClock(1,0)
clock_iterable = clock.iter_time_index()
clock = simclock.SimulationClock(1, 0)
clock_iterable = clock.iter_time_steps()
step_list = []
for step in clock_iterable:
step_list.append(step)
self.assertEqual(step_list, [0])
def test_iter_time_index_multisecond_step(self):
self.assertEqual(step_list, [])
def test_iter_time_steps_multisecond_step(self):
""" time step = 2, duration = 6"""
clock = simclock.SimulationClock(2,6)
clock_iterable = clock.iter_time_index()
clock = simclock.SimulationClock(2, 6)
clock_iterable = clock.iter_time_steps()
step_list = []
for step in clock_iterable:
step_list.append(step)
self.assertEqual(step_list, list(range(0,6//2+1)))
def test_iter_time_index_uneven_step(self):
self.assertEqual(step_list, list(range(1, 6 // 2 + 1)))
def test_iter_time_steps_uneven_step(self):
""" time step = 2, duration = 7"""
clock = simclock.SimulationClock(2,6)
clock_iterable = clock.iter_time_index()
clock = simclock.SimulationClock(2, 6)
clock_iterable = clock.iter_time_steps()
step_list = []
for step in clock_iterable: