0

My goal is to get a 2d-array (i.e. a matrix) of floats which can be visualized as a digital elevation model (i.e. a contour plot) with smooth contours as shown on the figure below provided that I am able to control how many white mountains are generated and their distance to each other every time I run the script.

The white mountains represent the clusters of cells in the CSV file containing values greater than 0.9 as shown on the color scale. On the figure we see two mountains divided by areas with values lower than 0.9. contours Here is my full code with detailed comments where I use convolutional filters to create such an image:

import numpy as np
import pandas as pd
from scipy import signal
import plotly
import plotly.graph_objs as go


def rndmtx():
    """Generate random 2d-array as a digital elevation model."""

    nx = 100
    ny = 100
    dem1 = np.random.rand(nx, ny)
    # Save array to csv file befor Gaussian filter.
    # Comment the next two lines if reading from the csv file.
    dafr = pd.DataFrame(dem1)
    dafr.to_csv('G_dem1.csv', header=False, index=False)

    # Uncomment the next two lines to read from csv file.
    # dafr = pd.read_csv('G_dem1.csv', header=None)
    # dem1 = dafr.values

    # Apply the first Gaussian filter.
    sizex = 5  # The less sizex and sizey the more highlands.
    sizey = 5  # The more sizex and sizey the more water.
    x, y = np.mgrid[-sizex:sizex+1, -sizey:sizey+1]
    scale = 0.33  # The more scale the bigger the difference in elevation.
    g = np.exp(-scale*(x**2/sizex+y**2/sizey))
    filter1 = g/g.sum()  # Normalise the Gaussian function.

    dem_smooth = signal.convolve(dem1, filter1, mode='valid')
    # Rescale so it lies between 0 and 1.
    dem_smooth = ((dem_smooth - dem_smooth.min())
                  / (dem_smooth.max() - dem_smooth.min()))

    # Apply the second Gaussian filter to make the boundaries smoother.
    sizex = 5
    sizey = 5
    x, y = np.mgrid[-sizex:sizex+1, -sizey:sizey+1]
    g = np.exp(-0.33*(x**2/sizex+y**2/sizey))
    filter2 = g/g.sum()

    dem_smooth1 = signal.convolve(dem_smooth, filter2, mode='valid')
    dem_smooth1 = ((dem_smooth1 - dem_smooth1.min())
                   / (dem_smooth1.max() - dem_smooth1.min()))

    return dem_smooth1

# Get the raw random array of the digital elevation model
#   and assign it to the variable.
contour_xy = rndmtx()

# Save the array into CSV file in the working directory.
df = pd.DataFrame(contour_xy)
df.to_csv('last_data.csv', header=False, index=False)

data = [
    go.Contour(
        z=contour_xy,
        colorscale=[
            [0, 'rgb(0, 161, 233)'], [0.28, 'rgb(0, 161, 233)'],
            [0.28, 'rgb(29, 210, 108)'], [0.50, 'rgb(29, 210, 108)'],
            [0.50, 'rgb(141, 232, 130)'], [0.65, 'rgb(141, 232, 130)'],
            [0.65, 'rgb(254, 254, 152)'], [0.75, 'rgb(254, 254, 152)'],
            [0.75, 'rgb(192, 182, 122)'], [0.82, 'rgb(192, 182, 122)'],
            [0.82, 'rgb(142, 110, 92)'], [0.88, 'rgb(142, 110, 92)'],
            [0.88, 'rgb(171, 147, 142)'], [0.93, 'rgb(171, 147, 142)'],
            [0.93, 'rgb(227, 219, 217)'], [0.97, 'rgb(227, 219, 217)'],
            [0.97, 'rgb(255, 255, 255)'], [1, 'rgb(255, 255, 255)']
        ],
    ),
]

layout = go.Layout(
    yaxis=dict(
        autorange='reversed'
    )
)

figure = go.Figure(data=data, layout=layout)
plotly.offline.plot(figure, filename='dem.html')

Every time I run the script I get a unique random array of numbers.

How to control over the number and proximity of white mountains to be generated?

For example, now I want 10 white mountains to be placed on the plot, then I want only 2 mountains, and so on.

Currently, I use a Brute Force algorithm that eventually build the plot with the desired number of mountains, but I am not able to set specific coordinates of mountains to control the distance between them.

Generous answers and pointing out the right direction are highly appreciated. Enjoy playing with the code!

Community
  • 1
  • 1
Max
  • 1,685
  • 16
  • 21
  • There's the pretty crude way of when you generate the numbers only generate up to 0.9 (non-inclusive) and then go around again with .9-1.0 values and overwrite however many random spaces on your map. There are edge cases here and you might get less desirable output but strictly speaking you will get what you asked for. – Gavin Achtemeier Aug 17 '17 at 20:45

1 Answers1

0

One of the clunky solutions is to populate some clusters of cells in the CSV file before Gaussian filters by values higher than 0.9, for example, all ones, as shown on the figure below.

This is just a CSV file opened in Excel with applied Color Scale feature. On the left is the random matrix. On the right you can see that I try to set four mountains of equal size 6x4 cells, all containing a value "1". matrices After applying convolutional filters, I get the following result: contour-try which is not good because I got only two mountains and two brown highlands. In addition, there are a lot of water. I want as less water as possible.

I do not mind rewriting the script from scratch if you can point out other techniques.

UPDATE

Thanks to the community, I finally came to a stable solution: build Scatter plot, then convert it into the Contour.

See question Convert Scatter to Contour, every dot turns into a highland

Max
  • 1,685
  • 16
  • 21