rivermapper/rivermapper.py

245 lines
4.8 KiB
Python
Executable File

#!/usr/bin/env python3
from heapq import heappush, heappop, heapify
import sys
import numpy as np
import imageio
file_input = None
file_output = None
n_args = len(sys.argv)
i = 1
n = 1
sea_level = -128
seed = None
contrast = 4.
bit_depth = 8
draw_linewidth = False
river_limit = 0
while i < n_args:
arg = sys.argv[i]
if len(arg) == 0:
i += 1
continue
if arg[0] == "-":
l = arg[1]
if l == "l":
sea_level = int(sys.argv[i+1])
i += 2
continue
if l == "s":
seed = int(sys.argv[i+1])
i += 2
continue
if l == "c":
contrast = float(sys.argv[i+1])
i += 2
continue
if l == "d":
bit_depth = int(sys.argv[i+1])
i += 2
continue
if l == "w":
draw_linewidth = True
river_limit = int(sys.argv[i+1])
i += 2
continue
if l == "i":
file_input = sys.argv[i+1]
i += 2
continue
if l == "o":
file_output = sys.argv[i+1]
i += 2
continue
else:
if n == 2:
file_output = arg
n += 1
if n == 1:
file_input = arg
n += 1
i += 1
if not file_input:
raise ValueError("No filename given for input")
if not file_output:
raise ValueError("No filename given for output")
if seed:
numpy.random.seed(seed=seed)
sys.setrecursionlimit(65536)
print("Reading image")
heightmap = np.array(imageio.imread(file_input))
shape = heightmap.shape
(X, Y) = shape
print("Finding start points")
visited = np.zeros(shape, dtype=bool)
start_points = []
def add_start_point(x, y):
start_points.append((heightmap[x, y] + np.random.random(), x, y))
visited[x, y] = True
to_explore = 0
for x in range(1, X-1):
for y in range(1, Y-1):
if heightmap[x, y] <= sea_level:
continue
to_explore += 1
if to_explore % 1000000 == 0:
print("Found", str(to_explore // 1000000), "millions points to explore")
if (heightmap[x-1, y] <= sea_level or heightmap[x+1, y] <= sea_level or heightmap[x, y-1] <= sea_level or heightmap[x, y+1] <= sea_level):
add_start_point(x, y)
for x in range(X):
if heightmap[x, 0] > sea_level:
add_start_point(x, 0)
to_explore += 1
if heightmap[x, -1] > sea_level:
add_start_point(x, Y-1)
to_explore += 1
for y in range(1, Y-1):
if heightmap[0, y] > sea_level:
add_start_point(0, y)
to_explore += 1
if heightmap[-1, y] > sea_level:
add_start_point(X-1, y)
to_explore += 1
print("Found", str(len(start_points)), "start points")
heap = start_points[:]
heapify(heap)
print("Building river trees:", str(to_explore), "points to visit")
flow_dirs = np.zeros(shape, dtype=np.int8)
# Directions:
# 1: +x
# 2: +y
# 4: -x
# 8: -y
def try_push(x, y): # try_push does 2 things at once: returning whether water can flow, and push the upward position in heap if yes.
if not visited[x, y]:
h = heightmap[x, y]
if h > sea_level:
heappush(heap, (h + np.random.random(), x, y))
visited[x, y] = True
return True
return False
def process_neighbors(x, y):
dirs = 0
if x > 0 and try_push(x-1, y):
dirs+= 1
if y > 0 and try_push(x, y-1):
dirs += 2
if x < X-1 and try_push(x+1, y):
dirs += 4
if y < Y-1 and try_push(x, y+1):
dirs += 8
flow_dirs[x, y] = dirs
while len(heap) > 0:
t = heappop(heap)
to_explore -= 1
if to_explore % 1000000 == 0:
print(str(to_explore // 1000000), "million points left", "Altitude:", int(t[0]), "Queue:", len(heap))
process_neighbors(t[1], t[2])
visited = None
heightmap = None
print("Calculating water quantity")
waterq = np.ones(shape)
def set_water(x, y):
water = 1
dirs = flow_dirs[x, y]
if dirs % 2 == 1:
water += set_water(x-1, y)
dirs //= 2
if dirs % 2 == 1:
water += set_water(x, y-1)
dirs //= 2
if dirs % 2 == 1:
water += set_water(x+1, y)
dirs //= 2
if dirs % 2 == 1:
water += set_water(x, y+1)
waterq[x, y] = water
return water
maxwater = 0
for start in start_points:
water = set_water(start[1], start[2])
if water > maxwater:
maxwater = water
print("Maximal water quantity:", str(maxwater))
flow_dirs = None
print("Generating image")
power = 1 / contrast
if draw_linewidth:
bit_depth = 1
river_array = np.zeros((X, Y), dtype=bool)
for x in range(X):
for y in range(Y):
q = waterq[x,y]
if q >= river_limit:
rsize = int((q / river_limit)**power)
if rsize > 1:
rsize -= 1
xmin = max(x-rsize, 0)
xmax = min(x+rsize+1, X)
ymin = max(y-rsize, 0)
ymax = min(y+rsize+1,Y)
river_array[xmin:xmax,y] = True
river_array[x,ymin:ymax] = True
else:
river_array[x,y] = True
data = np.uint8(river_array * 255)
else:
if bit_depth <= 8:
bit_depth = 8
dtype = np.uint8
elif bit_depth <= 16:
bit_depth = 16
dtype = np.uint16
elif bit_depth <= 32:
bit_depth = 32
dtype = np.uint32
else:
bit_depth = 64
dtype = np.uint64
maxvalue = 2 ** bit_depth - 1
coeff = maxvalue / (maxwater ** power)
data = np.floor((waterq ** power) * coeff).astype(dtype)
waterq = None
imageio.imwrite(file_output, data)