214 """Translate GDAL data type to WKT Raster pixel type."""
216 gdalc.GDT_Byte : { 'name': 'PT_8BUI', 'id': 4 },
217 gdalc.GDT_Int8 : { 'name': 'PT_8BSI', 'id': 3 },
218 gdalc.GDT_Int16 : { 'name': 'PT_16BSI', 'id': 5 },
219 gdalc.GDT_UInt16 : { 'name': 'PT_16BUI', 'id': 6 },
220 gdalc.GDT_Int32 : { 'name': 'PT_32BSI', 'id': 7 },
221 gdalc.GDT_UInt32 : { 'name': 'PT_32BUI', 'id': 8 },
222 gdalc.GDT_Float32 : { 'name': 'PT_32BF', 'id': 10 },
223 gdalc.GDT_Float64 : { 'name': 'PT_64BF', 'id': 11 }
226 if hasattr(gdalc, 'GDT_Float16'):
227 pixtypes[gdalc.GDT_Float16] = { 'name': 'PT_16BF', 'id': 9 }
229 # XXX: Uncomment these logs to debug types translation
230 #logit('MSG: Input GDAL pixel type: %s (%d)\n' % (gdal.GetDataTypeName(gdt), gdt))
231 #logit('MSG: Output WKTRaster pixel type: %(name)s (%(id)d)\n' % (pixtypes.get(gdt, 13)))
233 return pixtypes.get(gdt, 13)
236 """Translate GDAL data type to NumPy data type"""
238 gdalc.GDT_Byte : numpy.uint8,
239 gdalc.GDT_Int16 : numpy.int16,
240 gdalc.GDT_UInt16 : numpy.uint16,
241 gdalc.GDT_Int32 : numpy.int32,
242 gdalc.GDT_UInt32 : numpy.uint32,
243 gdalc.GDT_Float32: numpy.float32,
244 gdalc.GDT_Float64: numpy.float64
246 if hasattr(gdalc, 'GDT_Float16'):
247 ptnumpy[gdalc.GDT_Float16] = numpy.float16
248 return ptnumpy.get(pt, numpy.uint8)
392def make_sql_addrastercolumn(options, pixeltypes, nodata, pixelsize, blocksize, extent):
393 assert len(pixeltypes) > 0, "No pixel types given"
394 ts = make_sql_schema_table_names(options.table)
395 pt = make_sql_value_array(pixeltypes)
398 if nodata is not None and len(nodata) > 0:
399 nd = make_sql_value_array(nodata)
407 bs = ( 'null', 'null' )
408 # Check if regular blocking mode requested
409 if options.block_size is not None:
410 assert pixelsize is not None, "Pixel size is none, but regular blocking requested"
411 assert blocksize is not None, "Block size is none, but regular blocking requested"
412 assert extent is not None, "Extent is none, but regular blocking requested"
413 assert len(pixelsize) == 2, "Invalid pixel size, two values expected"
414 assert len(blocksize) == 2, "Invalid block size, two values expected"
415 assert len(extent) == 4, "Invalid extent, four coordinates expected"
416 assert len(extent[0]) == len(extent[3]) == 2, "Invalid extent, pair of X and Y pair expected"
418 bs = ( blocksize[0], blocksize[1] )
419 extgeom = "ST_Envelope(ST_SetSRID('POLYGON((%.15f %.15f,%.15f %.15f,%.15f %.15f,%.15f %.15f,%.15f %.15f))'::geometry, %d))" % \
420 (extent[0][0], extent[0][1], extent[1][0], extent[1][1],
421 extent[2][0], extent[2][1], extent[3][0], extent[3][1],
422 extent[0][0], extent[0][1], options.srid)
424 sql = "SELECT AddRasterColumn('%s','%s','%s',%d, %s, %s, %s, %s, %.15f, %.15f, %s, %s, %s);\n" % \
425 (ts[0], ts[1], options.column, options.srid, pt, odb, rb, nd,
426 pixelsize[0], pixelsize[1], bs[0], bs[1], extgeom)
428 logit("SQL: %s" % sql)
444def make_sql_create_raster_overviews(options):
445 schema = make_sql_schema_table_names(options.table)[0]
446 table = make_sql_full_table_name(schema + '.raster_overviews')
447 sql = 'CREATE TABLE ' + table + ' ( ' \
448 'o_table_catalog character varying(256) NOT NULL, ' \
449 'o_table_schema character varying(256) NOT NULL, ' \
450 'o_table_name character varying(256) NOT NULL, ' \
451 'o_column character varying(256) NOT NULL, ' \
452 'r_table_catalog character varying(256) NOT NULL, ' \
453 'r_table_schema character varying(256) NOT NULL, ' \
454 'r_table_name character varying(256) NOT NULL, ' \
455 'r_column character varying(256) NOT NULL, ' \
456 'out_db boolean NOT NULL, ' \
457 'overview_factor integer NOT NULL, ' \
458 'CONSTRAINT raster_overviews_pk ' \
459 'PRIMARY KEY (o_table_catalog, o_table_schema, o_table_name, o_column, overview_factor));\n'
464def make_sql_register_overview(options, ov_table, ov_factor):
465 assert len(ov_table) > 0
468 catalog = quote_sql_value('')
469 schema = make_sql_schema_table_names(options.table)[0]
470 r_table = make_sql_table_name(options.table)
472 sql = "INSERT INTO public.raster_overviews( " \
473 "o_table_catalog, o_table_schema, o_table_name, o_column, " \
474 "r_table_catalog, r_table_schema, r_table_name, r_column, out_db, overview_factor) " \
475 "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', FALSE, %d);\n" % \
476 (catalog, schema, ov_table, options.column, catalog, schema, r_table, options.column, ov_factor)
550def calculate_block_size(ds, band_from, band_to):
551 """Size of natural block reported by GDAL for bands of given dataset"""
554 for i in range(band_from, band_to):
555 band = ds.GetRasterBand(i)
556 assert band is not None, "Cannot access raster band %d" % i
557 dims = band.GetBlockSize()
559 # Assume bands with common block size
563 # Validate dimensions of bands block
564 if block_dims != dims:
565 logit("MSG: Block sizes don't match: %s != %s\n" % (str(block_dims), str(dims)))
567 assert block_dims is not None, "Failed to calculate block size"
568 return (int(block_dims[0]), int(block_dims[1]))
677def wkblify_raster_header(options, ds, level, ulp, xsize = None, ysize = None):
678 """Writes WKT Raster header based on given GDAL into HEX-encoded WKB."""
679 assert ds is not None, "Error: Missing GDAL dataset"
681 assert len(ulp) == 2, "Error: invalid upper-left corner"
683 if xsize is None or ysize is None:
684 assert xsize is None and ysize is None
685 xsize = ds.RasterXSize
686 ysize = ds.RasterYSize
688 # Collect GeoReference information
689 gt = get_gdal_geotransform(ds)
690 ul = calculate_geoxy(gt, (ulp[0], ulp[1]))
691 rt_ip = ( ul[0], ul[1] )
692 rt_skew = ( gt[2], gt[4] )
693 rt_scale = ( gt[1] * level, gt[5] * level )
695 # TODO: Any way to lookup for SRID based on SRS in WKT?
696 #srs = osr.SpatialReference()
697 #srs.ImportFromWkt(ds.GetProjection())
699 # Burn input raster as WKTRaster WKB format
702 hexwkb += wkblify('B', options.endian)
704 hexwkb += wkblify('H', options.version)
706 if options.band is not None and options.band > 0:
707 hexwkb += wkblify('H', 1)
709 hexwkb += wkblify('H', ds.RasterCount)
712 hexwkb += wkblify('d', rt_scale[0])
713 hexwkb += wkblify('d', rt_scale[1])
714 hexwkb += wkblify('d', rt_ip[0])
715 hexwkb += wkblify('d', rt_ip[1])
716 hexwkb += wkblify('d', rt_skew[0])
717 hexwkb += wkblify('d', rt_skew[1])
718 hexwkb += wkblify('i', options.srid)
719 check_hex(hexwkb, 57)
720 ### Number of columns and rows
721 hexwkb += wkblify('H', xsize)
722 hexwkb += wkblify('H', ysize)
723 check_hex(hexwkb, 61)
725 logit("MSG: Georeference: px = %s -> ul = %s \tresolution = %s \trotation = %s\n" \
726 % (str(ulp), str(rt_ip), str(rt_scale), str(rt_skew)))
758def wkblify_band(options, band, level, xoff, yoff, read_block_size, block_size, infile, bandidx):
759 """Writes band of given GDAL dataset into HEX-encoded WKB for WKT Raster output."""
760 assert band is not None, "Error: Missing GDAL raster band"
766 # TODO: Do we want to handle options.overview_level? --mloskot
768 # TODO: Where bandidx and ds come from? --mloskot
769 # ANSWER: Provided by caller method --jorgearevalo
770 hexwkb += wkblify('B', bandidx - 1)
771 filepath = os.path.abspath(infile.replace('\\', '\\\\'))
772 logit('MSG: Out-db raster path=%s\n' % filepath)
773 hexwkb += wkblify(str(len(filepath)) + 's', filepath)
774 hexwkb += wkblify('B', 0)
778 # Right most column and bottom most row of blocks have
779 # portions that extend beyond the raster
780 read_padding_size = calculate_block_pad_size(band, xoff, yoff, read_block_size)
781 valid_read_block_size = ( read_block_size[0] - read_padding_size[0],
782 read_block_size[1] - read_padding_size[1] )
785 if read_padding_size[0] > 0 or read_padding_size[1] > 0:
786 target_block_size = (valid_read_block_size[0] / level, valid_read_block_size[1] / level)
787 target_padding_size = (read_padding_size[0] / level, read_padding_size[1] / level)
789 target_block_size = block_size
790 target_padding_size = ( 0, 0 )
792 logit('MSG: Normalize read_block=%s for level=%d to valid_read_block=%s with padding=%s\n' % \
793 (read_block_size, level, valid_read_block_size, read_padding_size))
794 logit('MSG: Normalize target_block=%s for level=%d to valid_target_block=%s with padding=%s\n' % \
795 (block_size, level, target_block_size, target_padding_size))
796 logit('MSG: ReadAsArray( %d, %d, %s, %s)\n' % \
797 (xoff, yoff, str(valid_read_block_size), str(target_block_size)))
799 assert valid_read_block_size[0] > 0 and valid_read_block_size[1] > 0
800 assert target_block_size[0] > 0 and target_block_size[1] > 0
802 pixels = band.ReadAsArray(xoff, yoff, valid_read_block_size[0], valid_read_block_size[1],
803 target_block_size[0], target_block_size[1])
805 # XXX: Use for debugging only
806 #dump_block_numpy(pixels)
808 out_pixels = numpy.zeros((block_size[1], block_size[0]), pt2numpy(band.DataType))
810 logit('MSG: Read valid source:\t%d x %d\n' % (len(pixels[0]), len(pixels)))
811 logit('MSG: Write into block:\t%d x %d\n' % (len(out_pixels[0]), len(out_pixels)))
813 if target_padding_size[0] > 0 or target_padding_size[1] > 0:
815 ysize_read_pixels = len(pixels)
816 nodata_value = fetch_band_nodata(band)
818 # Apply columns padding
819 pad_cols = numpy.array([nodata_value] * target_padding_size[0])
820 for row in range (0, ysize_read_pixels):
821 out_line = numpy.append(pixels[row], pad_cols)
822 out_pixels[row] = out_line
824 # Fill rows padding with nodata value
825 for row in range(ysize_read_pixels, ysize_read_pixels + target_padding_size[1]):
826 out_pixels[row].fill(nodata_value)
830 # XXX: Use for debugging only
831 #dump_block_numpy(out_pixels)
833 hexwkb = binascii.hexlify(out_pixels)
838def wkblify_raster_level(options, ds, level, band_range, infile, i):
839 assert ds is not None
841 assert len(band_range) == 2
843 band_from = band_range[0]
844 band_to = band_range[1]
846 # Collect raster and block dimensions
847 raster_size = ( ds.RasterXSize, ds.RasterYSize )
848 if options.block_size is not None:
849 block_size = parse_block_size(options)
850 read_block_size = ( block_size[0] * level, block_size[1] * level)
851 grid_size = calculate_grid_size(raster_size, read_block_size)
853 block_size = raster_size # Whole raster as a single block
854 read_block_size = block_size
857 logit("MSG: Processing raster=%s using read_block_size=%s block_size=%s of grid=%s in level=%d\n" % \
858 (str(raster_size), str(read_block_size), str(block_size), str(grid_size), level))
860 # Register base raster in RASTER_COLUMNS - SELECT AddRasterColumn();
862 if i == 0 and options.create_table:
863 gt = get_gdal_geotransform(ds)
864 pixel_size = ( gt[1], gt[5] )
865 pixel_types = collect_pixel_types(ds, band_from, band_to)
866 nodata_values = collect_nodata_values(ds, band_from, band_to)
867 extent = calculate_bounding_box(ds, gt)
868 sql = make_sql_addrastercolumn(options, pixel_types, nodata_values,
869 pixel_size, block_size, extent)
870 options.output.write(sql)
871 gen_table = options.table
874 # Create overview table and register in RASTER_OVERVIEWS
876 # CREATE TABLE o_<LEVEL>_<NAME> ( rid serial, options.column RASTER )
877 schema_table_names = make_sql_schema_table_names(options.table)
878 level_table_name = 'o_' + str(level) + '_' + schema_table_names[1]
879 level_table = schema_table_names[0] + '.' + level_table_name
881 sql = make_sql_create_table(options, level_table, True)
882 options.output.write(sql)
883 sql = make_sql_register_overview(options, level_table_name, level)
884 options.output.write(sql)
885 gen_table = level_table
887 # Write (original) raster to hex binary output
891 for ycell in range(0, grid_size[1]):
892 for xcell in range(0, grid_size[0]):
894 xoff = xcell * read_block_size[0]
895 yoff = ycell * read_block_size[1]
897 logit("MSG: --------- CELL #%04d\tindex = %d x %d\tdim = (%d x %d)\t(%d x %d) \t---------\n" % \
898 (tile_count, xcell, ycell, xoff, yoff, xoff + read_block_size[0], yoff + read_block_size[1]))
900 if options.block_size is not None:
901 hexwkb = '' # Reset buffer as single INSERT per tile is generated
902 hexwkb += wkblify_raster_header(options, ds, level, (xoff, yoff),
903 block_size[0], block_size[1])
905 hexwkb += wkblify_raster_header(options, ds, level, (xoff, yoff))
907 for b in range(band_from, band_to):
908 band = ds.GetRasterBand(b)
909 assert band is not None, "Missing GDAL raster band %d" % b
910 logit("MSG: Band %d\n" % b)
912 hexwkb += wkblify_band_header(options, band)
913 hexwkb += wkblify_band(options, band, level, xoff, yoff, read_block_size, block_size, infile, b)
916 check_hex(hexwkb) # TODO: Remove to not to decrease performance
917 sql = make_sql_insert_raster(gen_table, options.column, hexwkb, options.filename, infile)
918 options.output.write(sql)
920 tile_count = tile_count + 1
922 return (gen_table, tile_count)
924def wkblify_raster(options, infile, i, previous_gt = None):
925 """Writes given raster dataset using GDAL features into HEX-encoded of
926 WKB for WKT Raster output."""
928 assert infile is not None, "Input file is none, expected file name"
929 assert options.version == g_rt_version, "Error: invalid WKT Raster protocol version"
930 assert options.endian == NDR, "Error: invalid endianness, use little-endian (NDR) only"
931 assert options.srid >= -1, "Error: do you really want to specify SRID = %d" % options.srid
933 # Open source raster file
934 ds = gdal.Open(infile, gdalc.GA_ReadOnly);
936 sys.exit('Error: Cannot open input file: ' + str(infile))
938 # By default, translate all raster bands
940 # Calculate range for single-band request
941 if options.band is not None and options.band > 0:
942 band_range = ( options.band, options.band + 1 )
944 band_range = ( 1, ds.RasterCount + 1 )
946 # Compare this px size with previous one
947 current_gt = get_gdal_geotransform(ds)
948 if previous_gt is not None:
949 if previous_gt[1] != current_gt[1] or previous_gt[5] != current_gt[5]:
950 sys.exit('Error: Cannot load raster with different pixel size in the same raster table')
952 # Generate requested overview level (base raster if level = 1)
953 summary = wkblify_raster_level(options, ds, options.overview_level, band_range, infile, i)
954 SUMMARY.append( summary )
965 (opts, args) = parse_command_line()
968 VERBOSE = opts.verbose
973 saved_out = sys.stdout
974 if isinstance(opts.output, str):
975 filename = opts.output
976 opts.output = open(filename, "w")
979 opts.output.write('BEGIN;\n')
981 # If overviews requested, CREATE TABLE raster_overviews
982 if opts.create_raster_overviews_table:
983 sql = make_sql_create_raster_overviews(opts)
984 opts.output.write(sql)
987 if opts.overview_level == 1:
990 sql = make_sql_drop_raster_table(opts.table)
991 opts.output.write(sql)
994 if opts.create_table and opts.overview_level == 1:
995 sql = make_sql_create_table(opts)
996 opts.output.write(sql)
1001 # Burn all specified input raster files into single WKTRaster table
1003 for infile in opts.raster:
1004 filelist = glob.glob(infile)
1005 assert len(filelist) > 0, "No input raster files found for '" + str(infile) + "'"
1007 for filename in filelist:
1008 logit("MSG: Dataset #%d: %s\n" % (i + 1, filename))
1010 # Write raster data to WKB and send it to opts.output
1011 gt = wkblify_raster(opts, filename.replace( '\\', '/') , i, gt)
1015 if opts.index and SUMMARY is not None:
1016 sql = make_sql_create_gist(SUMMARY[0][0], opts.column)
1017 opts.output.write(sql)
1020 opts.output.write('END;\n')
1023 if opts.vacuum and SUMMARY is not None:
1024 sql = make_sql_vacuum(SUMMARY[0][0])
1025 opts.output.write(sql)
1028 if opts.output != sys.stdout:
1029 sys.stdout = saved_out
1031 print("------------------------------------------------------------")
1032 print(" Summary of GDAL to PostGIS Raster processing:")
1033 print("------------------------------------------------------------")
1035 m = '%d (%s)' % (i, infile)
1038 print("Number of processed raster files: " + m)
1039 print("List of generated tables (number of tiles):")
1043 print("%d\t%s (%d)" % (i, s[0], s[1]))