diff --git a/requirements.txt b/requirements.txt index 47f81745..32d564e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pypng==0.0.18 PyPlatec==1.4.0 protobuf==3.0.0a3 six==1.10.0 +matplotlib \ No newline at end of file diff --git a/setup.py b/setup.py index 22866c32..da0cc8e7 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,8 @@ 'console_scripts': ['worldengine=worldengine.cli.main:main'], }, 'install_requires': ['PyPlatec==1.4.0', 'pypng>=0.0.18', 'numpy>=1.9.2, <= 1.10.0.post2', - 'argparse==1.2.1', 'noise==1.2.2', 'protobuf==3.0.0a3'], + 'argparse==1.2.1', 'noise==1.2.2', 'protobuf==3.0.0a3', + 'matplotlib'], 'license': 'MIT License' } diff --git a/tests/blessed_images/generated_blessed_images.py b/tests/blessed_images/generated_blessed_images.py index c5cd1993..d3083329 100644 --- a/tests/blessed_images/generated_blessed_images.py +++ b/tests/blessed_images/generated_blessed_images.py @@ -10,6 +10,7 @@ import os from worldengine.world import * from worldengine.draw import * +from worldengine.draw_plots import * from worldengine.image_io import PNGWriter diff --git a/tests/draw_test.py b/tests/draw_test.py index 30470fdb..d45f27b9 100644 --- a/tests/draw_test.py +++ b/tests/draw_test.py @@ -3,8 +3,8 @@ import numpy from worldengine.draw import _biome_colors, draw_simple_elevation, elevation_color, \ draw_elevation, draw_riversmap, draw_grayscale_heightmap, draw_ocean, draw_precipitation, \ - draw_world, draw_temperature_levels, draw_biome, draw_scatter_plot, draw_hypsographic_plot, \ - draw_satellite + draw_world, draw_temperature_levels, draw_biome, draw_scatter_plot, draw_satellite +from worldengine.draw_plots import draw_hypsographic_plot from worldengine.biome import Biome from worldengine.world import World from worldengine.image_io import PNGWriter, PNGReader @@ -156,12 +156,9 @@ def test_draw_scatter_plot(self): self._assert_img_equal("scatter_28070", target) def test_draw_hypsographic_plot(self): - x_bins = 800 - y_bins = 600 w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) - target = PNGWriter.grayscale_from_dimensions(x_bins, y_bins, channel_bitdepth=8) - draw_hypsographic_plot(w, target, x_bins, y_bins) - self._assert_img_equal("hypsographic_28070", target) + plot = draw_hypsographic_plot(w) + # self._assert_img_equal("hypsographic_28070", target) # TODO: find way to compare these def test_draw_satellite(self): w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) diff --git a/tox.ini b/tox.ini index b91be59e..99de69f8 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ deps = six pypng h5py + matplotlib [testenv] deps = diff --git a/worldengine/cli/main.py b/worldengine/cli/main.py index bfb80a6c..8ddee820 100644 --- a/worldengine/cli/main.py +++ b/worldengine/cli/main.py @@ -7,7 +7,8 @@ from worldengine.draw import draw_ancientmap_on_file, draw_biome_on_file, draw_ocean_on_file, \ draw_precipitation_on_file, draw_grayscale_heightmap_on_file, draw_simple_elevation_on_file, \ draw_temperature_levels_on_file, draw_riversmap_on_file, draw_scatter_plot_on_file, \ - draw_hypsographic_plot_on_file, draw_satellite_on_file, draw_icecaps_on_file + draw_satellite_on_file, draw_icecaps_on_file +from worldengine.draw_plots import draw_hypsographic_plot_on_file from worldengine.plates import world_gen, generate_plates_simulation from worldengine.imex import export from worldengine.step import Step diff --git a/worldengine/draw.py b/worldengine/draw.py index ad0cb54d..5e3a7fe4 100644 --- a/worldengine/draw.py +++ b/worldengine/draw.py @@ -734,64 +734,6 @@ def draw_scatter_plot(world, size, target): target.set_pixel(int(nx), (size - 1) - int(ny), (r, 128, b, 255)) -def draw_hypsographic_plot(world, target, x_bins, y_bins): - # Will draw a Hypsograhpic plot that contains information about the distribution of elevations in the world. - # y-axis shows the elevation, x-axis shows how often a certain elevation occurs. - # For further details see: - # https://en.wikipedia.org/wiki/Elevation#Hypsography - # http://www.ngdc.noaa.gov/mgg/global/etopo1_surface_histogram.html - - assert int(x_bins) > 0 and int(y_bins) > 0, "Hypsographic curve needs more than 0 bins." - - # set colors - color_none = target.get_max_colors() # white - color_full = int(color_none / 2) # gray - color_line = 0 # black - color_percent_sep = int(color_none * 0.8) # light-gray - - # Prepare a list of available elevations. Scale them to the y-axis and put them into a sorted list, - # smallest point to highest. - e = world.elevation['data'] - e_min = e.min() - e_max = e.max() - e = (e - e_min) / (e_max - e_min) # normalize to [0, 1] - e *= int(y_bins) - 0.01 # instead of "- 1", small trick to make the last bin contain more than one point - e = numpy.sort(e, axis=None) # flatten the array and order values by height - e = e.astype(dtype=numpy.uint16) # do not round (the graph wouldn't change, just shift) - - # Fill the plot. - # Start by calculating how many points of the heightmap have to be represented by a single bin (n). - # Then step through the bins and fill in points from highest to lowest. Every bin is set to the average height of - # the n points it represents. - # Instead of drawing to [y, x] draw to [y_bins - y - 1, x_bins - x - 1] to flip the output in x- and y-direction. - n = e.size / float(x_bins) - for x in range(x_bins): - avg = sum(e[int(x * n):int((x + 1) * n)]) - avg = int(avg / float(n)) # average height of n height-points - for y in range(y_bins): - target[y_bins - y - 1, x_bins - x - 1] = color_none if y > avg else color_full - - # Draw a vertical line every ten percent. - step = x_bins / 10.0 - for i in range(1, 10): # no lines at 0 and (x_bins - 1) - x = int(step * i) - for y in range(y_bins): - target[y, x_bins - x - 1] = color_percent_sep - - # Map sea-level etc. to appropriate y-positions in the graph. - lines = [] - for threshold in world.elevation['thresholds']: # see generation.py->initialize_ocean_and_thresholds() for details - if threshold[1] is None: - continue # TODO: remove if the thresholds are ever guaranteed to be numbers - lines.append(int(numpy.interp(threshold[1], [e_min, e_max], [0, y_bins]))) - - # Draw lines for sea-level, hill-level etc. The lines will get lighter with increasing height. - for i in range(len(lines)): - color = numpy.interp(i, [0, len(lines)], [color_line, color_none]) - for x in range(x_bins): - target[y_bins - lines[i] - 1, x] = color - - # ------------- # Draw on files # ------------- @@ -869,14 +811,6 @@ def draw_scatter_plot_on_file(world, filename): img.complete() -def draw_hypsographic_plot_on_file(world, filename): - x_bins = 800 - y_bins = 600 - img = PNGWriter.grayscale_from_dimensions(x_bins, y_bins, filename, channel_bitdepth=8) - draw_hypsographic_plot(world, img, x_bins, y_bins) - img.complete() - - def draw_satellite_on_file(world, filename): img = PNGWriter.rgba_from_dimensions(world.width, world.height, filename) draw_satellite(world, img) diff --git a/worldengine/draw_plots.py b/worldengine/draw_plots.py new file mode 100644 index 00000000..dfc250f4 --- /dev/null +++ b/worldengine/draw_plots.py @@ -0,0 +1,52 @@ +import numpy +import matplotlib.pyplot as plot + + +def draw_hypsographic_plot(world): + # Will draw a Hypsograhpic plot that contains information about the distribution of elevations in the world. + # y-axis shows the elevation, x-axis shows how often a certain elevation occurs. + # For further details see: + # https://en.wikipedia.org/wiki/Elevation#Hypsography + # http://www.ngdc.noaa.gov/mgg/global/etopo1_surface_histogram.html + + # Set up variables. + sea_level = world.sea_level() + fig = plot.figure() + p = fig.add_subplot(111) + + # Prepare a list of available elevations by putting them into a sorted list, smallest point to highest. + # 0 will refer to sea-level. + y = world.elevation['data'] - sea_level + y = numpy.sort(y, axis=None) # flatten the array and order values by height + + # Corresponding x-values. Inverted so the highest values are left instead of right. x refers to %, hence [0, 100]. + x = numpy.arange(100, 0, -100.0 / y.size) + + # Plot the data. + p.plot(x, y) + + # Cosmetics. + p.set_ylim(y.min(), y.max()) + p.fill_between(x, y.min(), y, color='0.8') + p.set_xlabel("Cumulative Area (% of World's Surface)") + p.set_ylabel("Elevation (no units)") + p.set_title("Hypsographic Curve - %s" % world.name) + + # Draw lines for sea-level, hill-level etc. The lines will get lighter with increasing height. + th = world.elevation['thresholds'] # see generation.py->initialize_ocean_and_thresholds() for details + for i in range(len(th)): + if th[i][1] is None: + continue + color = numpy.interp(i, [0, len(th)], [0, 1.0]) # grayscale values, black to white; white will never be used + p.axhline(th[i][1] - sea_level, color=str(color), linewidth=0.5) + p.text(99, th[i][1] - sea_level, th[i][0], fontsize=8, horizontalalignment='right', verticalalignment='bottom' if th[i][1] - sea_level > 0 else 'top') + + p.set_xticks(range(0, 101, 10)) + + return fig + + +def draw_hypsographic_plot_on_file(world, filename): + fig = draw_hypsographic_plot(world) + fig.savefig(filename) + plot.close(fig)