2018-01-08 04:50:14 -08:00
#!/usr/bin/env python3
2018-01-13 02:34:17 -08:00
# This script is made to convert a Digital Elevation Model image (usually GeoTIFF) into a database, readable by Minetest to generate real terrain.
2018-02-13 16:37:04 -08:00
import tkinter as tk
import tkinter . filedialog as fd
2018-02-20 08:36:31 -08:00
import tkinter . simpledialog as sd
import functools
2018-02-13 16:37:04 -08:00
2018-02-18 16:19:03 -08:00
import map_transform
import database
import rivers
2018-02-20 02:59:50 -08:00
from landcover import make_landcover
2018-02-18 16:19:03 -08:00
2018-02-13 16:37:04 -08:00
root = tk . Tk ( )
root . title ( " Geo Mapgen image converter " )
class WidgetGroup :
def get ( self ) :
return self . var . get ( )
def set ( self , v ) :
self . var . set ( v )
def set_state ( self , state ) :
for widget in self . widgets :
widget . config ( state = state )
2018-02-20 08:36:31 -08:00
def trace ( self , mode , callback ) :
self . var . trace ( mode , callback )
2018-02-13 16:37:04 -08:00
class FileEntry ( WidgetGroup ) :
def __init__ ( self , parent , iotype , row = 0 , column = 0 , columnspan = 1 , sticky = " W " , text = None , default = " " , dialog_text = " Open " ) :
self . var = tk . StringVar ( )
self . var . set ( default )
self . entry = tk . Entry ( parent , textvariable = self . var , width = 60 )
if iotype == " file " :
callback = self . browse_files
elif iotype == " dir " :
callback = self . browse_dirs
self . button = tk . Button ( parent , text = " Browse " , command = callback )
if text :
self . label = tk . Label ( parent , text = text )
self . label . grid ( row = row , column = column , sticky = sticky )
self . has_label = True
self . widgets = [ self . entry , self . button , self . label ]
column + = 1
else :
self . has_label = False
self . widgets = [ self . entry , self . button ]
self . entry . grid ( row = row , column = column , columnspan = columnspan )
column + = columnspan
self . button . grid ( row = row , column = column )
self . dialog_text = dialog_text
def browse_files ( self ) :
self . var . set ( fd . askopenfilename ( title = self . dialog_text ) )
def browse_dirs ( self ) :
self . var . set ( fd . askdirectory ( title = self . dialog_text ) )
class NumberEntry ( WidgetGroup ) :
def __init__ ( self , parent , mini , maxi , incr = 1 , row = 0 , column = 0 , columnspan = 1 , sticky = " W " , text = None , default = 0 , is_float = False ) :
if is_float :
self . var = tk . DoubleVar ( )
else :
self . var = tk . IntVar ( )
self . var . set ( default )
self . spinbox = tk . Spinbox ( parent , from_ = mini , to = maxi , increment = incr , textvariable = self . var , width = 8 )
if text :
self . label = tk . Label ( parent , text = text )
self . label . grid ( row = row , column = column , sticky = sticky )
self . has_label = True
self . widgets = [ self . spinbox , self . label ]
column + = 1
else :
self . has_label = False
self . widgets = [ self . spinbox ]
self . spinbox . grid ( row = row , column = column , columnspan = columnspan )
frame_files = tk . LabelFrame ( root , text = " I/O files " )
frame_files . pack ( )
frame_region = tk . LabelFrame ( root , text = " Region " )
frame_region . pack ( )
frame_params = tk . LabelFrame ( root , text = " Generic parameters " )
frame_params . pack ( )
2018-02-20 02:59:50 -08:00
frame_landcover = tk . LabelFrame ( root , text = " Land Cover " )
frame_landcover . pack ( )
2018-02-13 16:37:04 -08:00
frame_rivers = tk . LabelFrame ( root , text = " Rivers " )
frame_rivers . pack ( )
2018-02-20 08:36:31 -08:00
def input_projection ( mapname ) :
return sd . askinteger ( " Projection " , " GDAL has failed to detect projection automatically. \n Please set here the EPSG number of the projection \n used by " + mapname + " . " )
def file_map_update ( mapname , file_entry , * args ) :
fpath = file_entry . get ( )
map_transform . update_map ( mapname , fpath , get_proj = input_projection )
def get_update_callback ( entry , mapname ) :
return functools . partial ( file_map_update , mapname , entry )
2018-02-18 16:19:03 -08:00
input_entry = FileEntry ( frame_files , " file " , row = 0 , column = 0 , text = " Elevation image " , dialog_text = " Open elevation image " )
output_entry = FileEntry ( frame_files , " dir " , row = 1 , column = 0 , text = " Minetest world directory " , dialog_text = " Open Minetest world " )
2018-02-20 08:36:31 -08:00
input_entry . trace ( " w " , get_update_callback ( input_entry , " heightmap " ) )
2018-02-15 07:21:46 -08:00
def region_gui_update ( * args ) :
value = region_rb_var . get ( )
state1 = " disabled "
state2 = " disabled "
if value > = 1 :
state1 = " normal "
if value > = 2 :
state2 = " normal "
north_entry . set_state ( state1 )
east_entry . set_state ( state1 )
south_entry . set_state ( state1 )
west_entry . set_state ( state1 )
hscale_entry . set_state ( state2 )
region_rb_var = tk . IntVar ( )
region_rb_var . set ( 0 )
region_rb_var . trace ( " w " , region_gui_update )
region_rb1 = tk . Radiobutton ( frame_region , text = " Don ' t modify the image " , variable = region_rb_var , value = 0 )
region_rb2 = tk . Radiobutton ( frame_region , text = " Crop image " , variable = region_rb_var , value = 1 )
region_rb3 = tk . Radiobutton ( frame_region , text = " Crop and resample " , variable = region_rb_var , value = 2 )
region_rb1 . grid ( row = 0 , column = 0 , sticky = " W " )
region_rb2 . grid ( row = 1 , column = 0 , sticky = " W " )
region_rb3 . grid ( row = 2 , column = 0 , sticky = " W " )
north_entry = NumberEntry ( frame_region , - 90 , 90 , row = 3 , column = 1 , sticky = " E " , text = " N " , is_float = True )
west_entry = NumberEntry ( frame_region , - 180 , 180 , row = 4 , column = 0 , sticky = " E " , text = " W " , is_float = True )
east_entry = NumberEntry ( frame_region , - 180 , 180 , row = 4 , column = 2 , sticky = " E " , text = " E " , is_float = True )
south_entry = NumberEntry ( frame_region , - 90 , 90 , row = 5 , column = 1 , sticky = " E " , text = " S " , is_float = True )
hscale_entry = NumberEntry ( frame_region , 0 , 10000 , row = 6 , column = 0 , text = " Horizontal scale " , is_float = True )
2018-02-13 16:37:04 -08:00
map_size_label = tk . Label ( frame_region , text = " " )
2018-02-15 07:21:46 -08:00
2018-02-21 13:12:08 -08:00
def set_to_fullsize ( * args ) :
north , east , south , west = map_transform . get_map_bounds ( " heightmap " )
north_entry . set ( north )
east_entry . set ( east )
south_entry . set ( south )
west_entry . set ( west )
fullsize_button = tk . Button ( frame_region , text = " Full map size " , command = set_to_fullsize )
fullsize_button . grid ( row = 0 , column = 1 , rowspan = 3 , columnspan = 3 , sticky = " S " )
2018-02-15 07:21:46 -08:00
region_gui_update ( )
2018-02-18 16:19:03 -08:00
def update_parameters ( ) :
2018-02-15 07:21:46 -08:00
value = region_rb_var . get ( )
2018-02-18 16:19:03 -08:00
if value == 0 :
2018-02-19 03:32:01 -08:00
map_transform . set_parameters ( reproject = False , crop = False , reference = " heightmap " )
2018-02-18 16:19:03 -08:00
if value > = 1 :
if value == 2 :
reproject = True
else :
reproject = False
north , east , south , west , hscale = north_entry . get ( ) , east_entry . get ( ) , south_entry . get ( ) , west_entry . get ( ) , hscale_entry . get ( )
map_transform . set_parameters ( reproject = reproject , crop = True , region = ( north , east , south , west ) , hscale = hscale )
def map_size_update ( * args ) :
update_parameters ( )
2018-02-19 03:32:01 -08:00
npx , npy , _ , _ , _ = map_transform . get_map_size ( )
2018-02-13 16:37:04 -08:00
map_size_label . config ( text = " {:d} x {:d} " . format ( int ( npx ) , int ( npy ) ) )
2018-02-15 07:21:46 -08:00
2018-02-13 16:37:04 -08:00
calc_button = tk . Button ( frame_region , text = " Calculate size " , command = map_size_update )
2018-02-15 07:21:46 -08:00
map_size_label . grid ( row = 6 , column = 3 )
calc_button . grid ( row = 6 , column = 2 )
2018-02-13 16:37:04 -08:00
2018-02-18 16:19:03 -08:00
tile_size_entry = NumberEntry ( frame_params , 0 , 1024 , row = 0 , column = 0 , text = " Tiles size " , default = 80 )
scale_entry = NumberEntry ( frame_params , 0 , 1000 , row = 1 , column = 0 , text = " Vertical scale in meters per node " , default = 40 )
2018-02-13 16:37:04 -08:00
2018-02-20 02:59:50 -08:00
def landcover_gui_update ( * args ) :
if landcover_cb_var . get ( ) :
st = " normal "
else :
st = " disabled "
landcover_input_entry . set_state ( st )
landcover_legend_entry . set_state ( st )
landcover_cb_var = tk . BooleanVar ( )
landcover_cb_var . set ( False )
landcover_cb_var . trace ( " w " , landcover_gui_update )
landcover_cb = tk . Checkbutton ( frame_landcover , text = " Enable Land Cover " , variable = landcover_cb_var )
landcover_cb . grid ( row = 0 , column = 0 )
2018-02-20 16:43:56 -08:00
landcover_input_entry = FileEntry ( frame_landcover , " file " , row = 1 , column = 0 , text = " Land cover image " , dialog_text = " Open land cover image " )
landcover_legend_entry = FileEntry ( frame_landcover , " file " , row = 2 , column = 0 , text = " Land cover legend file " , dialog_text = " Open land cover legend " )
2018-02-20 08:36:31 -08:00
landcover_input_entry . trace ( " w " , get_update_callback ( landcover_input_entry , " landcover " ) )
2018-02-20 02:59:50 -08:00
landcover_gui_update ( )
2018-02-13 16:37:04 -08:00
def river_gui_update ( * args ) :
if river_cb_var . get ( ) :
rivermode_rb1 . config ( state = " normal " )
rivermode_rb2 . config ( state = " normal " )
if rivermode_rb_var . get ( ) == 1 :
st1 = " normal "
st2 = " disabled "
2018-02-03 14:47:53 -08:00
else :
2018-02-13 16:37:04 -08:00
st1 = " disabled "
st2 = " normal "
river_input_entry . set_state ( st1 )
river_limit_entry . set_state ( st2 )
river_hdiff_entry . set_state ( st2 )
river_power_entry . set_state ( st2 )
sea_level_entry . set_state ( st2 )
else :
st = " disabled "
rivermode_rb1 . config ( state = " disabled " )
rivermode_rb2 . config ( state = " disabled " )
river_input_entry . set_state ( st )
river_limit_entry . set_state ( st )
river_hdiff_entry . set_state ( st )
river_power_entry . set_state ( st )
sea_level_entry . set_state ( st )
river_cb_var = tk . BooleanVar ( )
2018-02-18 16:19:03 -08:00
river_cb_var . set ( False )
2018-02-13 16:37:04 -08:00
river_cb_var . trace ( " w " , river_gui_update )
river_cb = tk . Checkbutton ( frame_rivers , text = " Rivers " , variable = river_cb_var )
river_cb . grid ( row = 0 , column = 0 )
rivermode_rb_var = tk . IntVar ( )
2018-02-18 16:19:03 -08:00
rivermode_rb_var . set ( 0 )
2018-02-13 16:37:04 -08:00
rivermode_rb_var . trace ( " w " , river_gui_update )
rivermode_rb1 = tk . Radiobutton ( frame_rivers , text = " Load from file " , variable = rivermode_rb_var , value = 1 )
rivermode_rb1 . grid ( row = 1 , column = 0 )
2018-02-18 16:19:03 -08:00
river_input_entry = FileEntry ( frame_rivers , " file " , row = 1 , column = 1 , columnspan = 2 , dialog_text = " Open river image " )
2018-02-20 08:36:31 -08:00
river_input_entry . trace ( " w " , get_update_callback ( river_input_entry , " rivermap " ) )
2018-02-13 16:37:04 -08:00
rivermode_rb2 = tk . Radiobutton ( frame_rivers , text = " Calculate in-place (slow) " , variable = rivermode_rb_var , value = 0 )
rivermode_rb2 . grid ( row = 2 , column = 0 , rowspan = 4 )
2018-02-21 16:46:53 -08:00
river_limit_entry = NumberEntry ( frame_rivers , 0 , 1e6 , incr = 50 , row = 2 , column = 1 , text = " Minimal drainage basin " , default = 1000 )
2018-02-18 16:19:03 -08:00
river_hdiff_entry = NumberEntry ( frame_rivers , 0 , 100 , row = 3 , column = 1 , text = " Maximal height difference " , default = 40 , is_float = True )
river_power_entry = NumberEntry ( frame_rivers , 0 , 2 , incr = 0.05 , row = 4 , column = 1 , text = " River widening power " , default = 0.25 , is_float = True )
sea_level_entry = NumberEntry ( frame_rivers , - 32768 , 65535 , row = 5 , column = 1 , text = " Sea level " , default = - 128 )
2018-02-13 16:37:04 -08:00
river_gui_update ( )
def proceed ( ) :
fpath_output = output_entry . get ( )
2018-02-18 16:19:03 -08:00
fpath_output + = " /heightmap.dat "
fpath_conf = fpath_output + " .conf "
# Load files at the beginning, so that if a path is wrong, the user will know it instantly.
file_output = open ( fpath_output , " wb " )
file_conf = open ( fpath_conf , " w " )
update_parameters ( )
2018-02-19 03:32:01 -08:00
heightmap = map_transform . read_map ( " heightmap " , interp = 4 ) # Read with Lanczos interpolation (code 4)
2018-02-18 16:19:03 -08:00
if river_cb_var . get ( ) :
rivers_from_file = rivermode_rb_var . get ( ) == 1
if rivers_from_file :
2018-02-19 03:32:01 -08:00
rivermap = map_transform . read_map ( " rivers " , interp = 8 )
2018-02-18 16:19:03 -08:00
else :
river_limit = river_limit_entry . get ( )
river_power = river_power_entry . get ( )
sea_level = sea_level_entry . get ( )
max_river_hdiff = river_hdiff_entry . get ( )
rivermap = rivers . generate_rivermap ( heightmap , sea_level = sea_level , river_limit = river_limit , river_power = river_power )
else :
rivermap = None
2018-02-20 02:59:50 -08:00
if landcover_cb_var . get ( ) :
fpath_legend = landcover_legend_entry . get ( )
landmap_raw = map_transform . read_map ( " landcover " , interp = 0 )
landmap , legend = make_landcover ( landmap_raw , fpath_legend )
else :
landmap = None
legend = None
2018-02-18 16:19:03 -08:00
tile_size = tile_size_entry . get ( )
scale = scale_entry . get ( )
2018-02-20 02:59:50 -08:00
database . generate ( file_output , file_conf , heightmap , rivermap = rivermap , landmap = landmap , landmap_legend = legend , frag = tile_size , scale = scale )
2018-02-13 16:37:04 -08:00
proceed_button = tk . Button ( root , text = " Proceed " , command = proceed )
proceed_button . pack ( )
tk . mainloop ( )