Skip to content

Commit a770967

Browse files
committed
Overhauled GDAL export functionality and interface.
1 parent 56e8dba commit a770967

2 files changed

Lines changed: 97 additions & 45 deletions

File tree

worldengine/cli/main.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -341,18 +341,13 @@ def main():
341341
# -----------------------------------------------------
342342
export_options = parser.add_argument_group(
343343
"Export Options", "You can specify the formats you wish the generated output to be in. ")
344-
export_options.add_argument("--export-type", dest="export_type",
345-
help="Export to a specific format such as: BMP or PNG",
346-
default="bmp")
347-
export_options.add_argument("--export-bpp", dest="export_bpp", type=int,
348-
help="Bits per pixel: 8, 16 and 32",
349-
default=8)
350-
export_options.add_argument("--export-signed", dest="export_signed", action="store_true",
351-
help="Used signed bits or not.",
352-
default=False)
353-
export_options.add_argument("--normalize", dest="export_normalize", action="store_true",
354-
help="Normalize data to the min and max of your bpp choice.",
355-
default=False)
344+
export_options.add_argument("--export-format", dest="export_format", type=str,
345+
help="Export to a specific format such as BMP or PNG. " +
346+
"See http://www.gdal.org/formats_list.html for possible formats.",
347+
default="PNG")
348+
export_options.add_argument("--export-datatype", dest="export_datatype", type=str,
349+
help="Type of stored data, e.g. uint16, int32, float32 etc.",
350+
default="uint16")
356351

357352
args = parser.parse_args()
358353

@@ -371,7 +366,7 @@ def main():
371366
sys.setrecursionlimit(args.recursion_limit)
372367

373368
if args.number_of_plates < 1 or args.number_of_plates > 100:
374-
usage(error="Number of plates should be a in [1, 100]")
369+
usage(error="Number of plates should be in [1, 100]")
375370

376371
operation = "world"
377372
if args.OPERATOR is None:
@@ -498,7 +493,8 @@ def main():
498493
elif operation == 'export':
499494
world = load_world(args.FILE)
500495
print_world_info(world)
501-
export(world, args.export_type, args.export_bpp, args.export_signed, args.export_normalize)
496+
export(world, args.export_format, args.export_datatype,
497+
path = '%s/%s_elevation' % (args.output_dir, world_name))
502498
else:
503499
raise Exception(
504500
'Unknown operation: valid operations are %s' % OPERATIONS)

worldengine/imex/__init__.py

Lines changed: 87 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,65 +12,121 @@
1212
import sys
1313

1414

15-
def export(world, export_type, export_bpp, export_signed, export_normalize):
15+
'''
16+
Whenever a GDAL short-format (http://www.gdal.org/formats_list.html) is given
17+
and a unique mapping to a file suffix exists, it is looked up in gdal_mapper.
18+
19+
Trivial ones (i.e. a call to lower() does the job) are not handled:
20+
BAG, BMP, BT, ECW, ERS, FITS, GIF, GTA, PNG, RIK, VRT, XPM
21+
22+
All other formats (>100) currently end up with their respective GDAL short-format
23+
converted to lower-case and might need to be renamed by the user.
24+
'''
25+
gdal_mapper = { # TODO: Find a way to make GDAL provide this mapping.
26+
"aig" : "adf",
27+
"bsb" : "kap",
28+
"doq1" : "doq",
29+
"doq2" : "doq",
30+
"esat" : "n1",
31+
"grib" : "grb",
32+
"gtiff" : "tif",
33+
"hfa" : "img",
34+
"jdem" : "mem",
35+
"jpeg" : "jpg",
36+
"msgn" : "nat",
37+
"terragen": "ter",
38+
"usgsdem" : "dem",
39+
}
40+
41+
42+
def export(world, export_filetype = 'GTiff', export_datatype = 'float32', path = 'seed_output'):
1643
try:
1744
gdal
1845
except NameError:
1946
print("Cannot export: please install pygdal.")
2047
sys.exit(1)
2148

22-
final_driver = gdal.GetDriverByName(export_type)
49+
final_driver = gdal.GetDriverByName(export_filetype)
2350
if final_driver is None:
24-
print("%s driver not registered." % export_type)
51+
print("%s driver not registered." % export_filetype)
2552
sys.exit(1)
2653

27-
if export_bpp == 8 and export_signed:
28-
numpy_type = numpy.int8
29-
gdal_type = gdal.GDT_Byte
30-
elif export_bpp == 8 and not export_signed:
54+
# try to find the proper file-suffix
55+
export_filetype = export_filetype.lower()
56+
if export_filetype in gdal_mapper:
57+
export_filetype = gdal_mapper[export_filetype]
58+
59+
# Note: GDAL will throw informative errors on its own whenever file type and data type cannot be matched.
60+
61+
# translate export_datatype; http://www.gdal.org/gdal_8h.html#a22e22ce0a55036a96f652765793fb7a4
62+
export_datatype = export_datatype.lower()
63+
if export_datatype in ['gdt_byte', 'uint8', 'int8', 'byte', 'char']: # GDAL does not support int8
64+
bpp, signed, normalize = (8, False, True)
3165
numpy_type = numpy.uint8
32-
gdal_type = gdal.GDT_Byte
33-
elif export_bpp == 16 and export_signed:
34-
numpy_type = numpy.int16
35-
gdal_type = gdal.GDT_Int16
36-
elif export_bpp == 16 and not export_signed:
66+
gdal_type = gdal.GDT_Byte
67+
elif export_datatype in ['gdt_uint16', 'uint16']:
68+
bpp, signed, normalize = (16, False, True)
3769
numpy_type = numpy.uint16
38-
gdal_type = gdal.GDT_UInt16
39-
elif export_bpp == 32 and export_signed:
40-
numpy_type = numpy.int32
41-
gdal_type = gdal.GDT_Int32
42-
elif export_bpp == 32 and not export_signed:
70+
gdal_type = gdal.GDT_UInt16
71+
elif export_datatype in ['gdt_uint32', 'uint32']:
72+
bpp, signed, normalize = (32, False, True)
4373
numpy_type = numpy.uint32
44-
gdal_type = gdal.GDT_UInt32
74+
gdal_type = gdal.GDT_UInt32
75+
elif export_datatype in ['gdt_int16', 'int16']:
76+
bpp, signed, normalize = (16, True, True)
77+
numpy_type = numpy.int16
78+
gdal_type = gdal.GDT_Int16
79+
elif export_datatype in ['gdt_int32', 'int32', 'int']: # fallback for 'int'
80+
bpp, signed, normalize = (32, True, True)
81+
numpy_type = numpy.int32
82+
gdal_type = gdal.GDT_Int32
83+
elif export_datatype in ['gdt_float32', 'float32', 'float']: # fallback for 'float'
84+
bpp, signed, normalize = (32, True, False)
85+
numpy_type = numpy.float32
86+
gdal_type = gdal.GDT_Float32
87+
elif export_datatype in ['gdt_float64', 'float64']:
88+
bpp, signed, normalize = (64, True, False)
89+
numpy_type = numpy.float64
90+
gdal_type = gdal.GDT_Float64
4591
else:
46-
print ("BPP %d is not valid, we only support 8, 16 and 32." % export_bpp)
47-
sys.exit(1)
92+
raise TypeError("Type of data not recognized or not supported by GDAL: %s" % export_datatype)
4893

4994
# massage data to scale between the absolute min and max
50-
elevation = numpy.array(world.elevation['data'])
95+
elevation = numpy.copy(world.elevation['data'])
5196

52-
if not export_signed and elevation.min() < 0.0:
53-
elevation += abs(elevation.min()) # TODO: need better way to handle negative numbers
97+
# shift data according to minimum possible value
98+
if signed:
99+
elevation = elevation - world.sea_level() # elevation 0.0 now refers to sea-level
100+
else:
101+
elevation -= elevation.min() # lowest point at 0.0
54102

55-
if export_normalize:
56-
if export_signed:
57-
elevation *= (((2**export_bpp)/2)-1)/elevation.max()
103+
# rescale data (currently integer-types only)
104+
if normalize:
105+
# elevation maps usually have a range of 0 to 10, maybe 15 - rescaling for integers is essential
106+
if signed:
107+
elevation *= (2**(bpp - 1) - 1) / max(abs(elevation.min(), abs(elevation.max())))
58108
else:
59-
elevation *= (2**export_bpp)/elevation.max()
109+
elevation *= (2**bpp - 1) / abs(elevation.max())
110+
111+
# round data (integer-types only)
112+
if numpy_type != numpy.float32 and numpy_type != numpy.float64:
113+
elevation = elevation.round()
60114

115+
# switch to final data type; no rounding performed
61116
elevation = elevation.astype(numpy_type)
62117

63-
# take elevation data and push it into an intermediate GTiff format
118+
# take elevation data and push it into an intermediate GTiff format (some formats don't support being written by Create())
64119
inter_driver = gdal.GetDriverByName("GTiff")
65-
_, inter_file = tempfile.mkstemp()
66-
initial_ds = inter_driver.Create(inter_file, world.height, world.width, 1, gdal_type)
120+
_, inter_file = tempfile.mkstemp() # returns: (file-handle, absolute path)
121+
initial_ds = inter_driver.Create(inter_file, world.width, world.height, 1, gdal_type)
67122
band = initial_ds.GetRasterBand(1)
123+
68124
band.WriteArray(elevation)
69125
band = None # dereference band
70126
initial_ds = None # save/flush and close
71127

72128
# take the intermediate GTiff format and convert to final format
73129
initial_ds = gdal.Open(inter_file)
74-
final_driver.CreateCopy('seed_output-%d.%s' % (export_bpp, export_type), initial_ds)
130+
final_driver.CreateCopy('%s-%d.%s' % (path, bpp, export_filetype), initial_ds)
75131

76132
os.remove(inter_file)

0 commit comments

Comments
 (0)