1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import math
34 import os.path
35 import sys
36 import time
37
38 from osgeo import gdal
39
40 progress = gdal.TermProgress_nocb
41
42 __version__ = '$id$'[5:-1]
43
44
48
49
50 -def GetExtension(filename):
51 ext = os.path.splitext(filename)[1]
52 if ext.startswith('.'):
53 ext = ext[1:]
54 return ext
55
56
79
80
82 drv_list = GetOutputDriversFor(filename)
83 ext = GetExtension(filename)
84 if not drv_list:
85 if not ext:
86 return 'GTiff'
87 else:
88 raise Exception("Cannot guess driver for %s" % filename)
89 elif len(drv_list) > 1:
90 print("Several drivers matching %s extension. Using %s" % (ext if ext else '', drv_list[0]))
91 return drv_list[0]
92
93
94
95 -def raster_copy(s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
96 t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
97 nodata=None, verbose=0):
98
99 if verbose != 0:
100 print('Copy %d,%d,%d,%d to %d,%d,%d,%d.'
101 % (s_xoff, s_yoff, s_xsize, s_ysize,
102 t_xoff, t_yoff, t_xsize, t_ysize))
103
104 if nodata is not None:
105 return raster_copy_with_nodata(
106 s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
107 t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
108 nodata)
109
110 s_band = s_fh.GetRasterBand(s_band_n)
111 m_band = None
112
113
114 if s_band.GetMaskFlags() != gdal.GMF_ALL_VALID:
115 m_band = s_band.GetMaskBand()
116 elif s_band.GetColorInterpretation() == gdal.GCI_AlphaBand:
117 m_band = s_band
118 if m_band is not None:
119 return raster_copy_with_mask(
120 s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
121 t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
122 m_band)
123
124 s_band = s_fh.GetRasterBand(s_band_n)
125 t_band = t_fh.GetRasterBand(t_band_n)
126
127 data = s_band.ReadRaster(s_xoff, s_yoff, s_xsize, s_ysize,
128 t_xsize, t_ysize, t_band.DataType)
129 t_band.WriteRaster(t_xoff, t_yoff, t_xsize, t_ysize,
130 data, t_xsize, t_ysize, t_band.DataType)
131
132 return 0
133
134
135
136
137 -def raster_copy_with_nodata(s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
138 t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
139 nodata):
140 try:
141 import numpy as Numeric
142 except ImportError:
143 import Numeric
144
145 s_band = s_fh.GetRasterBand(s_band_n)
146 t_band = t_fh.GetRasterBand(t_band_n)
147
148 data_src = s_band.ReadAsArray(s_xoff, s_yoff, s_xsize, s_ysize,
149 t_xsize, t_ysize)
150 data_dst = t_band.ReadAsArray(t_xoff, t_yoff, t_xsize, t_ysize)
151
152 if not Numeric.isnan(nodata):
153 nodata_test = Numeric.equal(data_src, nodata)
154 else:
155 nodata_test = Numeric.isnan(data_src)
156
157 to_write = Numeric.choose(nodata_test, (data_src, data_dst))
158
159 t_band.WriteArray(to_write, t_xoff, t_yoff)
160
161 return 0
162
163
164
165
166 -def raster_copy_with_mask(s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
167 t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
168 m_band):
169 try:
170 import numpy as Numeric
171 except ImportError:
172 import Numeric
173
174 s_band = s_fh.GetRasterBand(s_band_n)
175 t_band = t_fh.GetRasterBand(t_band_n)
176
177 data_src = s_band.ReadAsArray(s_xoff, s_yoff, s_xsize, s_ysize,
178 t_xsize, t_ysize)
179 data_mask = m_band.ReadAsArray(s_xoff, s_yoff, s_xsize, s_ysize,
180 t_xsize, t_ysize)
181 data_dst = t_band.ReadAsArray(t_xoff, t_yoff, t_xsize, t_ysize)
182
183 mask_test = Numeric.equal(data_mask, 0)
184 to_write = Numeric.choose(mask_test, (data_src, data_dst))
185
186 t_band.WriteArray(to_write, t_xoff, t_yoff)
187
188 return 0
189
190
191
192
194 """
195 Translate a list of GDAL filenames, into file_info objects.
196
197 names -- list of valid GDAL dataset names.
198
199 Returns a list of file_info objects. There may be less file_info objects
200 than names if some of the names could not be opened as GDAL files.
201 """
202
203 file_infos = []
204 for name in names:
205 fi = file_info()
206 if fi.init_from_name(name) == 1:
207 file_infos.append(fi)
208
209 return file_infos
210
211
212
213
215 """A class holding information about a GDAL file."""
216
218 self.band_type = None
219 self.bands = None
220 self.ct = None
221 self.filename = None
222 self.geotransform = None
223 self.lrx = None
224 self.lry = None
225 self.projection = None
226 self.ulx = None
227 self.uly = None
228 self.xsize = None
229 self.ysize = None
230
232 """
233 Initialize file_info from filename
234
235 filename -- Name of file to read.
236
237 Returns 1 on success or 0 if the file can't be opened.
238 """
239 fh = gdal.Open(filename)
240 if fh is None:
241 return 0
242
243 self.filename = filename
244 self.bands = fh.RasterCount
245 self.xsize = fh.RasterXSize
246 self.ysize = fh.RasterYSize
247 self.band_type = fh.GetRasterBand(1).DataType
248 self.projection = fh.GetProjection()
249 self.geotransform = fh.GetGeoTransform()
250 self.ulx = self.geotransform[0]
251 self.uly = self.geotransform[3]
252 self.lrx = self.ulx + self.geotransform[1] * self.xsize
253 self.lry = self.uly + self.geotransform[5] * self.ysize
254
255 ct = fh.GetRasterBand(1).GetRasterColorTable()
256 if ct is not None:
257 self.ct = ct.Clone()
258 else:
259 self.ct = None
260
261 return 1
262
264 print('Filename: ' + self.filename)
265 print('File Size: %dx%dx%d'
266 % (self.xsize, self.ysize, self.bands))
267 print('Pixel Size: %f x %f'
268 % (self.geotransform[1], self.geotransform[5]))
269 print('UL:(%f,%f) LR:(%f,%f)'
270 % (self.ulx, self.uly, self.lrx, self.lry))
271
272 - def copy_into(self, t_fh, s_band=1, t_band=1, nodata_arg=None, verbose=0):
273 """
274 Copy this files image into target file.
275
276 This method will compute the overlap area of the file_info objects
277 file, and the target gdal.Dataset object, and copy the image data
278 for the common window area. It is assumed that the files are in
279 a compatible projection ... no checking or warping is done. However,
280 if the destination file is a different resolution, or different
281 image pixel type, the appropriate resampling and conversions will
282 be done (using normal GDAL promotion/demotion rules).
283
284 t_fh -- gdal.Dataset object for the file into which some or all
285 of this file may be copied.
286
287 Returns 1 on success (or if nothing needs to be copied), and zero one
288 failure.
289 """
290 t_geotransform = t_fh.GetGeoTransform()
291 t_ulx = t_geotransform[0]
292 t_uly = t_geotransform[3]
293 t_lrx = t_geotransform[0] + t_fh.RasterXSize * t_geotransform[1]
294 t_lry = t_geotransform[3] + t_fh.RasterYSize * t_geotransform[5]
295
296
297 tgw_ulx = max(t_ulx, self.ulx)
298 tgw_lrx = min(t_lrx, self.lrx)
299 if t_geotransform[5] < 0:
300 tgw_uly = min(t_uly, self.uly)
301 tgw_lry = max(t_lry, self.lry)
302 else:
303 tgw_uly = max(t_uly, self.uly)
304 tgw_lry = min(t_lry, self.lry)
305
306
307 if tgw_ulx >= tgw_lrx:
308 return 1
309 if t_geotransform[5] < 0 and tgw_uly <= tgw_lry:
310 return 1
311 if t_geotransform[5] > 0 and tgw_uly >= tgw_lry:
312 return 1
313
314
315 tw_xoff = int((tgw_ulx - t_geotransform[0]) / t_geotransform[1] + 0.1)
316 tw_yoff = int((tgw_uly - t_geotransform[3]) / t_geotransform[5] + 0.1)
317 tw_xsize = int((tgw_lrx - t_geotransform[0]) / t_geotransform[1] + 0.5) \
318 - tw_xoff
319 tw_ysize = int((tgw_lry - t_geotransform[3]) / t_geotransform[5] + 0.5) \
320 - tw_yoff
321
322 if tw_xsize < 1 or tw_ysize < 1:
323 return 1
324
325
326 sw_xoff = int((tgw_ulx - self.geotransform[0]) / self.geotransform[1] + 0.1)
327 sw_yoff = int((tgw_uly - self.geotransform[3]) / self.geotransform[5] + 0.1)
328 sw_xsize = int((tgw_lrx - self.geotransform[0]) /
329 self.geotransform[1] + 0.5) - sw_xoff
330 sw_ysize = int((tgw_lry - self.geotransform[3]) /
331 self.geotransform[5] + 0.5) - sw_yoff
332
333 if sw_xsize < 1 or sw_ysize < 1:
334 return 1
335
336
337 s_fh = gdal.Open(self.filename)
338
339 return raster_copy(s_fh, sw_xoff, sw_yoff, sw_xsize, sw_ysize, s_band,
340 t_fh, tw_xoff, tw_yoff, tw_xsize, tw_ysize, t_band,
341 nodata_arg, verbose)
342
343
344
346 print('Usage: gdal_merge.py [-o out_filename] [-of out_format] [-co NAME=VALUE]*')
347 print(' [-ps pixelsize_x pixelsize_y] [-tap] [-separate] [-q] [-v] [-pct]')
348 print(' [-ul_lr ulx uly lrx lry] [-init "value [value...]"]')
349 print(' [-n nodata_value] [-a_nodata output_nodata_value]')
350 print(' [-ot datatype] [-createonly] input_files')
351 print(' [--help-general]')
352 print('')
353
354
355 -def main(argv=None):
356 verbose = 0
357 quiet = 0
358 names = []
359 frmt = None
360 out_file = 'out.tif'
361
362 ulx = None
363 psize_x = None
364 separate = 0
365 copy_pct = 0
366 nodata = None
367 a_nodata = None
368 create_options = []
369 pre_init = []
370 band_type = None
371 createonly = 0
372 bTargetAlignedPixels = False
373 start_time = time.time()
374
375 gdal.AllRegister()
376 if argv is None:
377 argv = argv
378 argv = gdal.GeneralCmdLineProcessor(argv)
379 if argv is None:
380 sys.exit(0)
381
382
383 i = 1
384 while i < len(argv):
385 arg = argv[i]
386
387 if arg == '-o':
388 i = i + 1
389 out_file = argv[i]
390
391 elif arg == '-v':
392 verbose = 1
393
394 elif arg == '-q' or arg == '-quiet':
395 quiet = 1
396
397 elif arg == '-createonly':
398 createonly = 1
399
400 elif arg == '-separate':
401 separate = 1
402
403 elif arg == '-seperate':
404 separate = 1
405
406 elif arg == '-pct':
407 copy_pct = 1
408
409 elif arg == '-ot':
410 i = i + 1
411 band_type = gdal.GetDataTypeByName(argv[i])
412 if band_type == gdal.GDT_Unknown:
413 print('Unknown GDAL data type: %s' % argv[i])
414 sys.exit(1)
415
416 elif arg == '-init':
417 i = i + 1
418 str_pre_init = argv[i].split()
419 for x in str_pre_init:
420 pre_init.append(float(x))
421
422 elif arg == '-n':
423 i = i + 1
424 nodata = float(argv[i])
425
426 elif arg == '-a_nodata':
427 i = i + 1
428 a_nodata = float(argv[i])
429
430 elif arg == '-f' or arg == '-of':
431 i = i + 1
432 frmt = argv[i]
433
434 elif arg == '-co':
435 i = i + 1
436 create_options.append(argv[i])
437
438 elif arg == '-ps':
439 psize_x = float(argv[i + 1])
440 psize_y = -1 * abs(float(argv[i + 2]))
441 i = i + 2
442
443 elif arg == '-tap':
444 bTargetAlignedPixels = True
445
446 elif arg == '-ul_lr':
447 ulx = float(argv[i + 1])
448 uly = float(argv[i + 2])
449 lrx = float(argv[i + 3])
450 lry = float(argv[i + 4])
451 i = i + 4
452
453 elif arg[:1] == '-':
454 print('Unrecognized command option: %s' % arg)
455 Usage()
456 sys.exit(1)
457
458 else:
459 names.append(arg)
460
461 i = i + 1
462
463 if not names:
464 print('No input files selected.')
465 Usage()
466 sys.exit(1)
467
468 if frmt is None:
469 frmt = GetOutputDriverFor(out_file)
470
471 Driver = gdal.GetDriverByName(frmt)
472 if Driver is None:
473 print('Format driver %s not found, pick a supported driver.' % frmt)
474 sys.exit(1)
475
476 DriverMD = Driver.GetMetadata()
477 if 'DCAP_CREATE' not in DriverMD:
478 print('Format driver %s does not support creation and piecewise writing.\nPlease select a format that does, such as GTiff (the default) or HFA (Erdas Imagine).' % frmt)
479 sys.exit(1)
480
481
482 file_infos = names_to_fileinfos(names)
483
484 if ulx is None:
485 ulx = file_infos[0].ulx
486 uly = file_infos[0].uly
487 lrx = file_infos[0].lrx
488 lry = file_infos[0].lry
489
490 for fi in file_infos:
491 ulx = min(ulx, fi.ulx)
492 uly = max(uly, fi.uly)
493 lrx = max(lrx, fi.lrx)
494 lry = min(lry, fi.lry)
495
496 if psize_x is None:
497 psize_x = file_infos[0].geotransform[1]
498 psize_y = file_infos[0].geotransform[5]
499
500 if band_type is None:
501 band_type = file_infos[0].band_type
502
503
504 gdal.PushErrorHandler('CPLQuietErrorHandler')
505 t_fh = gdal.Open(out_file, gdal.GA_Update)
506 gdal.PopErrorHandler()
507
508
509 if t_fh is None:
510
511 if bTargetAlignedPixels:
512 ulx = math.floor(ulx / psize_x) * psize_x
513 lrx = math.ceil(lrx / psize_x) * psize_x
514 lry = math.floor(lry / -psize_y) * -psize_y
515 uly = math.ceil(uly / -psize_y) * -psize_y
516
517 geotransform = [ulx, psize_x, 0, uly, 0, psize_y]
518
519 xsize = int((lrx - ulx) / geotransform[1] + 0.5)
520 ysize = int((lry - uly) / geotransform[5] + 0.5)
521
522 if separate != 0:
523 bands = 0
524
525 for fi in file_infos:
526 bands = bands + fi.bands
527 else:
528 bands = file_infos[0].bands
529
530 t_fh = Driver.Create(out_file, xsize, ysize, bands,
531 band_type, create_options)
532 if t_fh is None:
533 print('Creation failed, terminating gdal_merge.')
534 sys.exit(1)
535
536 t_fh.SetGeoTransform(geotransform)
537 t_fh.SetProjection(file_infos[0].projection)
538
539 if copy_pct:
540 t_fh.GetRasterBand(1).SetRasterColorTable(file_infos[0].ct)
541 else:
542 if separate != 0:
543 bands = 0
544 for fi in file_infos:
545 bands = bands + fi.bands
546 if t_fh.RasterCount < bands:
547 print('Existing output file has less bands than the input files. You should delete it before. Terminating gdal_merge.')
548 sys.exit(1)
549 else:
550 bands = min(file_infos[0].bands, t_fh.RasterCount)
551
552
553 if a_nodata is not None:
554 for i in range(t_fh.RasterCount):
555 t_fh.GetRasterBand(i + 1).SetNoDataValue(a_nodata)
556
557
558 if pre_init is not None:
559 if t_fh.RasterCount <= len(pre_init):
560 for i in range(t_fh.RasterCount):
561 t_fh.GetRasterBand(i + 1).Fill(pre_init[i])
562 elif len(pre_init) == 1:
563 for i in range(t_fh.RasterCount):
564 t_fh.GetRasterBand(i + 1).Fill(pre_init[0])
565
566
567 t_band = 1
568
569 if quiet == 0 and verbose == 0:
570 progress(0.0)
571 fi_processed = 0
572
573 for fi in file_infos:
574 if createonly != 0:
575 continue
576
577 if verbose != 0:
578 print("")
579 print("Processing file %5d of %5d, %6.3f%% completed in %d minutes."
580 % (fi_processed + 1, len(file_infos),
581 fi_processed * 100.0 / len(file_infos),
582 int(round((time.time() - start_time) / 60.0))))
583 fi.report()
584
585 if separate == 0:
586 for band in range(1, bands + 1):
587 fi.copy_into(t_fh, band, band, nodata, verbose)
588 else:
589 for band in range(1, fi.bands + 1):
590 fi.copy_into(t_fh, band, t_band, nodata, verbose)
591 t_band = t_band + 1
592
593 fi_processed = fi_processed + 1
594 if quiet == 0 and verbose == 0:
595 progress(fi_processed / float(len(file_infos)))
596
597
598 t_fh = None
599
600
601 if __name__ == '__main__':
602 sys.exit(main(sys.argv))
603