I haven’t seen a ton of great examples of making maps with perlin noise in python. So here we go!

The dancing crocodile in my garden thanks you for sharing.

Perlin noise is a mathematical formula used to generate ‘realistic’ structures. It’s noise but unlike regular noise it has some coherent structure. You can think of perlin noise as multiple samples of a noisy field sampled at different resolutions and layered on top of each other. Here is regular noise vs. Perlin noise:

In the python noise module there are a few parameters that affect what you see when you generate your perlin noise:

**scale**: number that determines at what distance to view the noisemap.**octaves**: the number of levels of detail you want you perlin noise to have.**lacunarity**: number that determines how much detail is added or removed at each octave (adjusts frequency).**persistence**: number that determines how much each octave contributes to the overall shape (adjusts amplitude).

We won’t worry about scale too much, you can use it to zoom out (bigger scale) or in (smaller scale).

Perlin noise combines multiple functions called ‘octaves’ to produce natural looking surfaces. Each octave adds a layer of detail to the surface. For example: octave 1 could be mountains, octave 2 could be boulders, octave 3 could be the rocks.

Lacunarity of more than 1 means that each octave will increase its level of fine grained detail (increased frqeuency). Lacunarity of 1 means that each octave will have the sam level of detail. Lacunarity of less than one means that each octave will get smoother. The last two are usually undesirable so a lacunarity of 2 works quite well.

Persistence determines how much each octave contributes to the overall structure of the noise map. If your persistence is 1 all octaves contribute equally. If you persistence is more than 1 successive octaves contribute more and you get something closer to regular noise (the regular noise image above is actually a perlin noise with a persistence of 5.0). A more default setting would be a persistence of less than 1.0 which will decrease the effect of later octaves.

Enough chatting though! Let’s run some experiments. First let’s start with default perlin noise, and its accompanying image:

import noise | |

import numpy as np | |

from scipy.misc import toimage | |

shape = (1024,1024) | |

scale = 100.0 | |

octaves = 6 | |

persistence = 0.5 | |

lacunarity = 2.0 | |

world = np.zeros(shape) | |

for i in range(shape[0]): | |

for j in range(shape[1]): | |

world[i][j] = noise.pnoise2(i/scale, | |

j/scale, | |

octaves=octaves, | |

persistence=persistence, | |

lacunarity=lacunarity, | |

repeatx=1024, | |

repeaty=1024, | |

base=0) | |

toimage(world).show() |

The way this perlin noise looks in our script is a 2D array of values between -1 and 1. The values that are darker on the map are lower values, the values that are close to 1 are lighter. What I want to try next is assigning two colors to different ranges of values in this map to produce some terrain:

blue = [65,105,225] | |

green = [34,139,34] | |

beach = [238, 214, 175] | |

def add_color(world): | |

color_world = np.zeros(world.shape+(3,)) | |

for i in range(shape[0]): | |

for j in range(shape[1]): | |

if world[i][j] < -0.05: | |

color_world[i][j] = blue | |

elif world[i][j] < 0: | |

color_world[i][j] = beach | |

elif world[i][j] < 1.0: | |

color_world[i][j] = green | |

return color_world | |

color_world = add_color(world) | |

toimage(color_world).show() |

This terrain map is pretty neat; it has jagged coasts, beaches, and lots of water. while I have never observed natural terrain that looks like this if we look at any one part of the map it seems ‘realistic.’ Let’s take it a step further and add mountains and snow:

blue = [65,105,225] | |

green = [34,139,34] | |

beach = [238, 214, 175] | |

snow = [255, 250, 250] | |

mountain = [139, 137, 137] | |

def add_color(world): | |

color_world = np.zeros(world.shape+(3,)) | |

for i in range(shape[0]): | |

for j in range(shape[1]): | |

if world[i][j] < -0.05: | |

color_world[i][j] = blue | |

elif world[i][j] < 0: | |

color_world[i][j] = beach | |

elif world[i][j] < 1.0: | |

color_world[i][j] = green | |

elif world[i][j] < 0.35: | |

color_world[i][j] = mountain | |

elif world[i][j] < 1.0: | |

color_world[i][j] = snow | |

return color_world | |

color_world = add_color(world) | |

toimage(color_world).show() |

This is cool but this terrain pattern is clearly not natural. To make it more natural we will use a circular filter to get rid of all the preipheral perlin noise:

a,b = shape[0]/2, shape[1]/2 | |

n = 1024 | |

r = 125 | |

y,x = np.ogrid[-a:n-a, -b:n-b] | |

# creates a mask with True False values | |

# at indices | |

mask = x**2+y**2 <= r**2 | |

black = [0, 0, 0] | |

island_world = np.zeros_like(color_world) | |

for i in range(shape[0]): | |

for j in range(shape[1]): | |

if mask[i][j]: | |

island_world[i][j] = color_world[i][j] | |

else: | |

island_world[i][j] = black | |

toimage(island_world).show() |

Here I was trying to create an island so I made a circular filter and then applied it to the color_world perlin noise array. What I ended up was a planet floating in an ocean. I changed the ocean color to black and it looks pretty cool! That said what I wanted was an island so let’s try again. This time we’re going to calculate a circular gradient and then apply that over the perlin noise as a filter.

**Circular Gradient:**

import math | |

center_x, center_y = shape[1] // 2, shape[0] // 2 | |

circle_grad = np.zeros_like(world) | |

for y in range(world.shape[0]): | |

for x in range(world.shape[1]): | |

distx = abs(x - center_x) | |

disty = abs(y - center_y) | |

dist = math.sqrt(distx*distx + disty*disty) | |

circle_grad[y][x] = dist | |

# get it between -1 and 1 | |

max_grad = np.max(circle_grad) | |

circle_grad = circle_grad / max_grad | |

circle_grad -= 0.5 | |

circle_grad *= 2.0 | |

circle_grad = -circle_grad | |

# shrink gradient | |

for y in range(world.shape[0]): | |

for x in range(world.shape[1]): | |

if circle_grad[y][x] > 0: | |

circle_grad[y][x] *= 20 | |

# get it between 0 and 1 | |

max_grad = np.max(circle_grad) | |

circle_grad = circle_grad / max_grad | |

toimage(circle_grad).show() |

I struggled a lot with this part. I’m sure there is a more efficient way to get the gradient like this but the above was what I came up with. I calculated a distance metric from the center of the map and then normalized, shrunk, and renomalized those distances to produce this spherical gradient. Again lighter means the value is closer to 1, darker colors are closer to 0. Next I apply this circular gradient to the perlin noise we created before.

**Circular Gradient + Noise:**

world_noise = np.zeros_like(world) | |

for i in range(shape[0]): | |

for j in range(shape[1]): | |

world_noise[i][j] = (world[i][j] * circle_grad[i][j]) | |

if world_noise[i][j] > 0: | |

world_noise[i][j] *= 20 | |

# get it between 0 and 1 | |

max_grad = np.max(world_noise) | |

world_noise = world_noise / max_grad | |

toimage(world_noise).show() |

This part was less tricky but still a pain. I multiply the perlin noise by the circle gradient and then I increase the contrast by multiplying positive (lighter values) by 20. Then I renormalize to make it 0–1 again.

**Colorized**** Circular Gradient + Noise:**

lightblue = [0,191,255] | |

blue = [65,105,225] | |

green = [34,139,34] | |

darkgreen = [0,100,0] | |

sandy = [210,180,140] | |

beach = [238, 214, 175] | |

snow = [255, 250, 250] | |

mountain = [139, 137, 137] | |

threshold = 0 | |

def add_color2(world): | |

color_world = np.zeros(world.shape+(3,)) | |

for i in range(shape[0]): | |

for j in range(shape[1]): | |

if world[i][j] < threshold + 0.05: | |

color_world[i][j] = blue | |

elif world[i][j] < threshold + 0.055: | |

color_world[i][j] = sandy | |

elif world[i][j] < threshold + 0.1: | |

color_world[i][j] = beach | |

elif world[i][j] < threshold + 0.25: | |

color_world[i][j] = green | |

elif world[i][j] < threshold + 0.6: | |

color_world[i][j] = darkgreen | |

elif world[i][j] < threshold + 0.7: | |

color_world[i][j] = mountain | |

elif world[i][j] < threshold + 1.0: | |

color_world[i][j] = snow | |

return color_world | |

island_world_grad = add_color2(world_noise) | |

toimage(island_world_grad).show() |

This is really cool and it looks like a much more natural archipelago. I encourage you to try different shading methods and maybe randomly removing some sections. I’m going to change the threshold value and set it as `threshold = 0.2`

. That will produce a smaller but more realistic archipelago as so:

There we are! We have a natural looking island archipelago! So now that we have our islands you may notice that no matter how often you rerun this script perlin noise will produce the same islands. To get new islands you can set the `base`

parameter of the pnoise2 function to a random integer number, let’s try `base=5, base=100`

:

#### Conclusion

So we started with some simple noise and ended up with a way to generate a realtively unlimited number of unique and natural looking archipelagos! I hope you enjoyed this post!

## Create your profile

## Only paying subscribers can comment on this post

Log in## Check your email

For your security, we need to re-authenticate you.

Click the link we sent to , or click here to log in.