| 1 | #! /usr/bin/env python |
|---|
| 2 | # |
|---|
| 3 | # $Id$ |
|---|
| 4 | # |
|---|
| 5 | # This is a simple utility used to dump GDAL dataset into HEX WKB stream. |
|---|
| 6 | # It's considered as a prototype of raster2pgsql tool planned to develop |
|---|
| 7 | # in future. |
|---|
| 8 | # For more details about raster2pgsql tool, see Specification page: |
|---|
| 9 | # http://trac.osgeo.org/postgis/wiki/WKTRaster |
|---|
| 10 | # |
|---|
| 11 | # The script requires Python bindings for GDAL. |
|---|
| 12 | # Available at http://trac.osgeo.org/gdal/wiki/GdalOgrInPython |
|---|
| 13 | # |
|---|
| 14 | ################################################################################ |
|---|
| 15 | # Copyright (C) 2009 Mateusz Loskot <mateusz@loskot.net>, Pierre Racine <pierre.racine@sbf.ulaval.ca>, |
|---|
| 16 | # Jorge Arevalo <jorge.arevalo@deimos-space.com> |
|---|
| 17 | # |
|---|
| 18 | # This program is free software; you can redistribute it and/or modify |
|---|
| 19 | # it under the terms of the GNU General Public License as published by |
|---|
| 20 | # the Free Software Foundation; either version 3 of the License, or |
|---|
| 21 | # (at your option) any later version. |
|---|
| 22 | # |
|---|
| 23 | # This program is distributed in the hope that it will be useful, |
|---|
| 24 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 25 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 26 | # GNU General Public License for more details. |
|---|
| 27 | # |
|---|
| 28 | # You should have received a copy of the GNU General Public License |
|---|
| 29 | # along with this program; if not, write to the Free Software |
|---|
| 30 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|---|
| 31 | ################################################################################ |
|---|
| 32 | # |
|---|
| 33 | from osgeo import gdal |
|---|
| 34 | from osgeo import osr |
|---|
| 35 | import osgeo.gdalconst as gdalc |
|---|
| 36 | from optparse import OptionParser, OptionGroup |
|---|
| 37 | import binascii |
|---|
| 38 | import glob |
|---|
| 39 | import math |
|---|
| 40 | import numpy |
|---|
| 41 | import os |
|---|
| 42 | import sys |
|---|
| 43 | |
|---|
| 44 | ################################################################################ |
|---|
| 45 | # CONSTANTS - DO NOT CHANGE |
|---|
| 46 | |
|---|
| 47 | # Endianness enumeration |
|---|
| 48 | NDR = 1 # Little-endian |
|---|
| 49 | XDR = 0 # Big-endian |
|---|
| 50 | |
|---|
| 51 | # Default version of WKTRaster protocol. |
|---|
| 52 | # WARNING: Currently, this is the only valid value |
|---|
| 53 | # and option -w, --wktraster-version is ignored, if specified. |
|---|
| 54 | g_rt_version = 0 |
|---|
| 55 | |
|---|
| 56 | # Default format of binary output is little-endian (NDR) |
|---|
| 57 | # WARNING: Currently, big-endian (XDR) output is not supported |
|---|
| 58 | # and option -e, --endian is ignored, if specified. |
|---|
| 59 | g_rt_endian = NDR |
|---|
| 60 | |
|---|
| 61 | # Default name of column, overriden with -f, --field option. |
|---|
| 62 | g_rt_column = 'rast' |
|---|
| 63 | |
|---|
| 64 | g_rt_catalog = '' |
|---|
| 65 | g_rt_schema = 'public' |
|---|
| 66 | |
|---|
| 67 | ################################################################################ |
|---|
| 68 | # UTILITIES |
|---|
| 69 | VERBOSE = False |
|---|
| 70 | SUMMARY = [] |
|---|
| 71 | |
|---|
| 72 | def parse_command_line(): |
|---|
| 73 | """Collects, parses and validates command line arguments.""" |
|---|
| 74 | |
|---|
| 75 | prs = OptionParser(version="%prog $Revision$") |
|---|
| 76 | |
|---|
| 77 | # Mandatory parameters |
|---|
| 78 | grp0 = OptionGroup(prs, "Source and destination", |
|---|
| 79 | "*** Mandatory parameters always required ***") |
|---|
| 80 | grp0.add_option("-r", "--raster", dest="raster", action="append", default=None, |
|---|
| 81 | help="append raster to list of input files, at least one raster " |
|---|
| 82 | "file required. You may use wildcards (?,*) for specifying multiple files.") |
|---|
| 83 | grp0.add_option("-t", "--table", dest="table", action="store", default=None, |
|---|
| 84 | help="raster destination in form of [<schema>.]<table> or base raster table for overview level>1, required") |
|---|
| 85 | prs.add_option_group(grp0); |
|---|
| 86 | |
|---|
| 87 | # Optional parameters - raster manipulation |
|---|
| 88 | grp_r = OptionGroup(prs, "Raster processing", |
|---|
| 89 | "Optional parameters used to manipulate input raster dataset") |
|---|
| 90 | grp_r.add_option("-s", "--srid", dest="srid", action="store", type="int", default=-1, |
|---|
| 91 | help="assign output raster with specified SRID") |
|---|
| 92 | grp_r.add_option("-b", "--band", dest="band", action="store", type="int", default=None, |
|---|
| 93 | help="specify number of band to be extracted from raster file") |
|---|
| 94 | grp_r.add_option("-k", "--block-size", dest="block_size", action="store", default=None, |
|---|
| 95 | help="uses regular blocking mode with size of block used to tile input raster, specified as WIDTHxHEIGHT") |
|---|
| 96 | grp_r.add_option("-R", "--register", dest="register", action="store_true", default=False, |
|---|
| 97 | help="register the raster as a filesystem (out-db) raster") |
|---|
| 98 | grp_r.add_option("-l", "--overview-level", dest="overview_level", action="store", type="int", default=1, |
|---|
| 99 | help='Create overview tables named as o_<LEVEL>_<RASTER_TABLE> and ' |
|---|
| 100 | 'populate with GDAL-provided overviews (regular blocking only)') |
|---|
| 101 | prs.add_option_group(grp_r); |
|---|
| 102 | |
|---|
| 103 | # Optional parameters - database/table manipulation |
|---|
| 104 | grp_t = OptionGroup(prs, 'Database processing', |
|---|
| 105 | 'Optional parameters used to manipulate database objects') |
|---|
| 106 | grp_t.add_option('-c', '--create', dest='create_table', action='store_true', default=True, |
|---|
| 107 | help='create new table and populate it with raster(s), this is the default mode') |
|---|
| 108 | grp_t.add_option("-d", "--drop", dest="drop_table", action="store_true", default=False, |
|---|
| 109 | help="drop table, create new one and populate with raster(s)") |
|---|
| 110 | grp_t.add_option("-f", "--field", dest="column", action="store", default=g_rt_column, |
|---|
| 111 | help="specify name of raster data column, default is 'rast'") |
|---|
| 112 | grp_t.add_option("-F", "--filename", dest="filename", action="store_true", default=False, |
|---|
| 113 | help="insert a column with the name of the file") |
|---|
| 114 | grp_t.add_option("-I", "--index", dest="index", action="store_true", default=False, |
|---|
| 115 | help="create a GiST index on the raster column") |
|---|
| 116 | grp_t.add_option("-M", "--vacuum", dest="vacuum", action="store_true", default=False, |
|---|
| 117 | help="issue VACUUM command against all generated tables") |
|---|
| 118 | grp_t.add_option('-V', '--create-raster-overviews', dest='create_raster_overviews_table', |
|---|
| 119 | action='store_true', default=False, |
|---|
| 120 | help='create RASTER_OVERVIEWS table used to store overviews metadata') |
|---|
| 121 | prs.add_option_group(grp_t); |
|---|
| 122 | |
|---|
| 123 | # Other optional parameters |
|---|
| 124 | grp_u = OptionGroup(prs, "Miscellaneous", "Other optional parameters") |
|---|
| 125 | grp_u.add_option("-e", "--endian", dest="endian", action="store", type="int", default=g_rt_endian, |
|---|
| 126 | help="control endianness of generated binary output of raster; " |
|---|
| 127 | "specify 0 for XDR and 1 for NDR (default); " |
|---|
| 128 | "only NDR output is supported now") |
|---|
| 129 | grp_u.add_option("-w", "--wktraster-version", dest="version", |
|---|
| 130 | action="store", type="int", default=g_rt_version, |
|---|
| 131 | help="specify version of WKT Raster protocol, default is 0; " |
|---|
| 132 | "only value of zero is supported now") |
|---|
| 133 | grp_u.add_option("-o", "--output", dest="output", action="store", default=sys.stdout, |
|---|
| 134 | help="specify file for conversion output, otherwise send to stdout") |
|---|
| 135 | grp_u.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, |
|---|
| 136 | help="be excessively verbose and useful for debugging") |
|---|
| 137 | prs.add_option_group(grp_u); |
|---|
| 138 | |
|---|
| 139 | (opts, args) = prs.parse_args() |
|---|
| 140 | |
|---|
| 141 | # Validate options |
|---|
| 142 | if opts.create_table and opts.drop_table: |
|---|
| 143 | prs.error("options -c and -d are mutually exclusive") |
|---|
| 144 | if not opts.create_table and not opts.drop_table: |
|---|
| 145 | opts.create_table = True |
|---|
| 146 | |
|---|
| 147 | if opts.raster is None: |
|---|
| 148 | prs.error("use option -r to specify at least one input raster. Wildcards (?,*) are accepted.") |
|---|
| 149 | |
|---|
| 150 | if opts.block_size is not None and len(opts.raster) != 1: |
|---|
| 151 | prs.error("regular blocking supports single-raster input only") |
|---|
| 152 | |
|---|
| 153 | if opts.block_size is not None: |
|---|
| 154 | if len(opts.block_size.split('x')) != 2 and len(opts.block_size.split('X')) != 2: |
|---|
| 155 | prs.error("invalid format of block size, expected WIDTHxHEIGHT") |
|---|
| 156 | |
|---|
| 157 | if opts.overview_level > 1 and opts.block_size is None: |
|---|
| 158 | prs.error("regular blocking mode required to enable overviews support (level > 1)") |
|---|
| 159 | |
|---|
| 160 | if opts.create_raster_overviews_table and opts.overview_level <= 1: |
|---|
| 161 | prs.error('create table for RASTER_OVERVIEWS available only if overviews import requested') |
|---|
| 162 | |
|---|
| 163 | # XXX: Now, if --band=Nth, then only Nth band of all specified rasters is dumped/imported |
|---|
| 164 | # This behavior can be changed to support only single-raster input if --band option used. |
|---|
| 165 | #if opts.band is not None and len(opts.raster) > 1: |
|---|
| 166 | # prs.error("option -b requires single input raster only, specify -r option only once") |
|---|
| 167 | |
|---|
| 168 | if opts.table is None: |
|---|
| 169 | prs.error("use option -t to specify raster destination table") |
|---|
| 170 | if len(opts.table.split('.')) > 2: |
|---|
| 171 | prs.error("invalid format of table name specified with option -t, expected [<schema>.]table") |
|---|
| 172 | |
|---|
| 173 | if opts.output is None: |
|---|
| 174 | prs.error("failed to initialise output file, try to use option -o explicitly") |
|---|
| 175 | |
|---|
| 176 | if opts.version is not None: |
|---|
| 177 | if opts.version != g_rt_version: |
|---|
| 178 | prs.error("invalid version of WKT Raster protocol specified, only version 0 is supported") |
|---|
| 179 | else: |
|---|
| 180 | prs.error("use option -w to specify version of WKT Raster protocol") |
|---|
| 181 | |
|---|
| 182 | if opts.endian is not None: |
|---|
| 183 | if opts.endian != NDR and opts.endian != XDR: |
|---|
| 184 | prs.error("invalid endianness value, valid ones are 0 for NDR or 1 for XDR") |
|---|
| 185 | else: |
|---|
| 186 | prs.error("use option -e to specify endianness of binary output") |
|---|
| 187 | |
|---|
| 188 | return (opts, args) |
|---|
| 189 | |
|---|
| 190 | |
|---|
| 191 | def logit(msg): |
|---|
| 192 | """If verbose mode requested, sends extra progress information to stderr""" |
|---|
| 193 | if VERBOSE is True: |
|---|
| 194 | sys.stderr.write(msg) |
|---|
| 195 | |
|---|
| 196 | |
|---|
| 197 | def gdt2pt(gdt): |
|---|
| 198 | """Translate GDAL data type to WKT Raster pixel type.""" |
|---|
| 199 | pixtypes = { |
|---|
| 200 | gdalc.GDT_Byte : { 'name': 'PT_8BUI', 'id': 4 }, |
|---|
| 201 | gdalc.GDT_Int16 : { 'name': 'PT_16BSI', 'id': 5 }, |
|---|
| 202 | gdalc.GDT_UInt16 : { 'name': 'PT_16BUI', 'id': 6 }, |
|---|
| 203 | gdalc.GDT_Int32 : { 'name': 'PT_32BSI', 'id': 7 }, |
|---|
| 204 | gdalc.GDT_UInt32 : { 'name': 'PT_32BUI', 'id': 8 }, |
|---|
| 205 | gdalc.GDT_Float32 : { 'name': 'PT_32BF', 'id': 10 }, |
|---|
| 206 | gdalc.GDT_Float64 : { 'name': 'PT_64BF', 'id': 11 } |
|---|
| 207 | } |
|---|
| 208 | |
|---|
| 209 | # XXX: Uncomment these logs to debug types translation |
|---|
| 210 | #logit('MSG: Input GDAL pixel type: %s (%d)\n' % (gdal.GetDataTypeName(gdt), gdt)) |
|---|
| 211 | #logit('MSG: Output WKTRaster pixel type: %(name)s (%(id)d)\n' % (pixtypes.get(gdt, 13))) |
|---|
| 212 | |
|---|
| 213 | return pixtypes.get(gdt, 13) |
|---|
| 214 | |
|---|
| 215 | def pt2numpy(pt): |
|---|
| 216 | """Translate GDAL data type to NumPy data type""" |
|---|
| 217 | ptnumpy = { |
|---|
| 218 | gdalc.GDT_Byte : numpy.uint8, |
|---|
| 219 | gdalc.GDT_Int16 : numpy.int16, |
|---|
| 220 | gdalc.GDT_UInt16 : numpy.uint16, |
|---|
| 221 | gdalc.GDT_Int32 : numpy.int32, |
|---|
| 222 | gdalc.GDT_UInt32 : numpy.uint32, |
|---|
| 223 | gdalc.GDT_Float32: numpy.float32, |
|---|
| 224 | gdalc.GDT_Float64: numpy.float64 |
|---|
| 225 | } |
|---|
| 226 | return ptnumpy.get(pt, numpy.uint8) |
|---|
| 227 | |
|---|
| 228 | def pt2fmt(pt): |
|---|
| 229 | """Returns binary data type specifier for given pixel type.""" |
|---|
| 230 | fmttypes = { |
|---|
| 231 | 4: 'B', # PT_8BUI |
|---|
| 232 | 5: 'h', # PT_16BSI |
|---|
| 233 | 6: 'H', # PT_16BUI |
|---|
| 234 | 7: 'i', # PT_32BSI |
|---|
| 235 | 8: 'I', # PT_32BUI |
|---|
| 236 | 10: 'f', # PT_32BF |
|---|
| 237 | 11: 'd' # PT_64BF |
|---|
| 238 | } |
|---|
| 239 | return fmttypes.get(pt, 'x') |
|---|
| 240 | |
|---|
| 241 | |
|---|
| 242 | def fmt2printfmt(fmt): |
|---|
| 243 | """Returns printf-like formatter for given binary data type sepecifier.""" |
|---|
| 244 | fmttypes = { |
|---|
| 245 | 'B': '%d', # PT_8BUI |
|---|
| 246 | 'h': '%d', # PT_16BSI |
|---|
| 247 | 'H': '%d', # PT_16BUI |
|---|
| 248 | 'i': '%d', # PT_32BSI |
|---|
| 249 | 'I': '%d', # PT_32BUI |
|---|
| 250 | 'f': '%f', # PT_32BF |
|---|
| 251 | 'd': '%f', # PT_64BF |
|---|
| 252 | 's': '%s' |
|---|
| 253 | } |
|---|
| 254 | return fmttypes.get(fmt, 'f') |
|---|
| 255 | |
|---|
| 256 | def parse_block_size(options): |
|---|
| 257 | assert options is not None |
|---|
| 258 | assert options.block_size is not None |
|---|
| 259 | |
|---|
| 260 | wh = options.block_size.split('x') |
|---|
| 261 | if len(wh) != 2: |
|---|
| 262 | wh = options.block_size.split('X') |
|---|
| 263 | |
|---|
| 264 | assert len(wh) == 2, "invalid format of specified block size" |
|---|
| 265 | return ( int(wh[0]), int(wh[1]) ) |
|---|
| 266 | |
|---|
| 267 | ################################################################################ |
|---|
| 268 | # SQL OPERATIONS |
|---|
| 269 | |
|---|
| 270 | def quote_sql_value(value): |
|---|
| 271 | assert value is not None, "None value given" |
|---|
| 272 | |
|---|
| 273 | if len(value) > 0 and value[0] != "'" and value[:-1] != "'": |
|---|
| 274 | sql = "'" + str(value) + "'" |
|---|
| 275 | else: |
|---|
| 276 | sql = value |
|---|
| 277 | return sql |
|---|
| 278 | |
|---|
| 279 | def quote_sql_name(name): |
|---|
| 280 | assert name is not None, "None name given" |
|---|
| 281 | |
|---|
| 282 | if name[0] != "\"" and name[:-1] != "\"": |
|---|
| 283 | sql = "\"" + str(name) + "\"" |
|---|
| 284 | else: |
|---|
| 285 | sql = name |
|---|
| 286 | return sql |
|---|
| 287 | |
|---|
| 288 | def make_sql_value_array(values): |
|---|
| 289 | sql = "ARRAY[" |
|---|
| 290 | for v in values: |
|---|
| 291 | if type(v) == str: |
|---|
| 292 | sql += quote_sql_value(v) + "," |
|---|
| 293 | else: |
|---|
| 294 | sql += str(v) + ',' |
|---|
| 295 | sql = sql[:-1] # Trim comma |
|---|
| 296 | sql += "]" |
|---|
| 297 | return sql |
|---|
| 298 | |
|---|
| 299 | def make_sql_schema_table_names(schema_table): |
|---|
| 300 | st = schema_table.split('.') |
|---|
| 301 | if len(st) == 1: |
|---|
| 302 | # TODO: Should we warn user that public is used implicitly? |
|---|
| 303 | st.insert(0, "public") |
|---|
| 304 | assert len(st) == 2, "Invalid format of table name, expected [<schema>.]table" |
|---|
| 305 | return (st[0], st[1]) |
|---|
| 306 | |
|---|
| 307 | def make_sql_full_table_name(schema_table): |
|---|
| 308 | st = make_sql_schema_table_names(schema_table) |
|---|
| 309 | table = "\"%s\".\"%s\"" % (st[0], st[1]) |
|---|
| 310 | return table |
|---|
| 311 | |
|---|
| 312 | def make_sql_table_name(schema_table): |
|---|
| 313 | st = schema_table.split('.') |
|---|
| 314 | assert len(st) == 1 or len(st) == 2, "Invalid format of table name, expected [<schema>.]table" |
|---|
| 315 | if len(st) == 2: |
|---|
| 316 | return st[1] |
|---|
| 317 | return st[0] |
|---|
| 318 | |
|---|
| 319 | def make_sql_drop_table(table): |
|---|
| 320 | sql = "DROP TABLE IF EXISTS %s CASCADE;\n" \ |
|---|
| 321 | % make_sql_full_table_name(table) |
|---|
| 322 | logit("SQL: %s" % sql) |
|---|
| 323 | return sql |
|---|
| 324 | |
|---|
| 325 | def make_sql_drop_raster_table(table): |
|---|
| 326 | st = make_sql_schema_table_names(table) |
|---|
| 327 | |
|---|
| 328 | if len(st[0]) == 0: |
|---|
| 329 | target = "'', '%s'" % st[1] |
|---|
| 330 | else: |
|---|
| 331 | target = "'%s', '%s'" % (st[0], st[1]) |
|---|
| 332 | sql = "SELECT DropRasterTable(%s);\n" % target |
|---|
| 333 | logit("SQL: %s" % sql) |
|---|
| 334 | return sql |
|---|
| 335 | |
|---|
| 336 | |
|---|
| 337 | def make_sql_create_table(options, table = None, is_overview = False): |
|---|
| 338 | |
|---|
| 339 | if table is None: |
|---|
| 340 | table = options.table |
|---|
| 341 | |
|---|
| 342 | if options.filename: |
|---|
| 343 | sql = "CREATE TABLE %s (rid serial PRIMARY KEY, \"filename\" text);\n" \ |
|---|
| 344 | % (make_sql_full_table_name(table)) |
|---|
| 345 | else: |
|---|
| 346 | if options.overview_level > 1 and is_overview: |
|---|
| 347 | sql = "CREATE TABLE %s (rid serial PRIMARY KEY, %s RASTER);\n" \ |
|---|
| 348 | % (make_sql_full_table_name(table), quote_sql_name(options.column)) |
|---|
| 349 | else: |
|---|
| 350 | sql = "CREATE TABLE %s (rid serial PRIMARY KEY);\n" \ |
|---|
| 351 | % (make_sql_full_table_name(table)) |
|---|
| 352 | |
|---|
| 353 | logit("SQL: %s" % sql) |
|---|
| 354 | return sql |
|---|
| 355 | |
|---|
| 356 | |
|---|
| 357 | def make_sql_create_gist(table, column): |
|---|
| 358 | gist_table = make_sql_table_name(table) |
|---|
| 359 | target_table = make_sql_full_table_name(table) |
|---|
| 360 | logit("MSG: Create GiST spatial index on %s\n" % gist_table) |
|---|
| 361 | |
|---|
| 362 | sql = "CREATE INDEX \"%s_%s_gist_idx\" ON %s USING GIST (st_convexhull(%s));\n" % \ |
|---|
| 363 | (gist_table, column, target_table, column) |
|---|
| 364 | logit("SQL: %s" % sql) |
|---|
| 365 | return sql; |
|---|
| 366 | |
|---|
| 367 | |
|---|
| 368 | def make_sql_addrastercolumn(options, pixeltypes, nodata, pixelsize, blocksize, extent): |
|---|
| 369 | assert len(pixeltypes) > 0, "No pixel types given" |
|---|
| 370 | ts = make_sql_schema_table_names(options.table) |
|---|
| 371 | pt = make_sql_value_array(pixeltypes) |
|---|
| 372 | |
|---|
| 373 | nd = 'null' |
|---|
| 374 | if nodata is not None and len(nodata) > 0: |
|---|
| 375 | nd = make_sql_value_array(nodata) |
|---|
| 376 | |
|---|
| 377 | odb = 'false' |
|---|
| 378 | if options.register: |
|---|
| 379 | odb = 'true' |
|---|
| 380 | |
|---|
| 381 | rb = 'false' |
|---|
| 382 | extgeom = 'null' |
|---|
| 383 | bs = ( 'null', 'null' ) |
|---|
| 384 | # Check if regular blocking mode requested |
|---|
| 385 | if options.block_size is not None: |
|---|
| 386 | assert pixelsize is not None, "Pixel size is none, but regular blocking requested" |
|---|
| 387 | assert blocksize is not None, "Block size is none, but regular blocking requested" |
|---|
| 388 | assert extent is not None, "Extent is none, but regular blocking requested" |
|---|
| 389 | assert len(pixelsize) == 2, "Invalid pixel size, two values expected" |
|---|
| 390 | assert len(blocksize) == 2, "Invalid block size, two values expected" |
|---|
| 391 | assert len(extent) == 4, "Invalid extent, four coordinates expected" |
|---|
| 392 | assert len(extent[0]) == len(extent[3]) == 2, "Invalid extent, pair of X and Y pair expected" |
|---|
| 393 | rb = 'true' |
|---|
| 394 | bs = ( blocksize[0], blocksize[1] ) |
|---|
| 395 | extgeom = "ST_Envelope(ST_SetSRID('POLYGON((%f %f,%f %f,%f %f,%f %f,%f %f))'::geometry, %d))" % \ |
|---|
| 396 | (extent[0][0], extent[0][1], extent[1][0], extent[1][1], |
|---|
| 397 | extent[2][0], extent[2][1], extent[3][0], extent[3][1], |
|---|
| 398 | extent[0][0], extent[0][1], options.srid) |
|---|
| 399 | |
|---|
| 400 | sql = "SELECT AddRasterColumn('%s','%s','%s',%d, %s, %s, %s, %s, %f, %f, %s, %s, %s);\n" % \ |
|---|
| 401 | (ts[0], ts[1], options.column, options.srid, pt, odb, rb, nd, |
|---|
| 402 | pixelsize[0], pixelsize[1], bs[0], bs[1], extgeom) |
|---|
| 403 | |
|---|
| 404 | logit("SQL: %s" % sql) |
|---|
| 405 | return sql |
|---|
| 406 | |
|---|
| 407 | def make_sql_insert_raster(table, rast, hexwkb, insert_filename, file): |
|---|
| 408 | if insert_filename: |
|---|
| 409 | assert file is not None, "Missing filename, but insert_filename requested" |
|---|
| 410 | sql = "INSERT INTO %s ( filename, %s ) VALUES ( (\'%s\')::text, (\'%s\')::raster );\n" \ |
|---|
| 411 | % (make_sql_full_table_name(table), rast, file, hexwkb) |
|---|
| 412 | else: |
|---|
| 413 | sql = "INSERT INTO %s ( %s ) VALUES ( (\'%s\')::raster );\n" \ |
|---|
| 414 | % (make_sql_full_table_name(table), rast, hexwkb) |
|---|
| 415 | |
|---|
| 416 | # NOTE: Usually, no need for such detailed verbosity |
|---|
| 417 | #logit("SQL: %s" % sql) |
|---|
| 418 | return sql |
|---|
| 419 | |
|---|
| 420 | |
|---|
| 421 | def make_sql_create_raster_overviews(options): |
|---|
| 422 | schema = make_sql_schema_table_names(options.table)[0] |
|---|
| 423 | table = make_sql_full_table_name(schema + '.raster_overviews') |
|---|
| 424 | sql = 'CREATE TABLE ' + table + ' ( ' \ |
|---|
| 425 | 'o_table_catalog character varying(256) NOT NULL, ' \ |
|---|
| 426 | 'o_table_schema character varying(256) NOT NULL, ' \ |
|---|
| 427 | 'o_table_name character varying(256) NOT NULL, ' \ |
|---|
| 428 | 'o_column character varying(256) NOT NULL, ' \ |
|---|
| 429 | 'r_table_catalog character varying(256) NOT NULL, ' \ |
|---|
| 430 | 'r_table_schema character varying(256) NOT NULL, ' \ |
|---|
| 431 | 'r_table_name character varying(256) NOT NULL, ' \ |
|---|
| 432 | 'r_column character varying(256) NOT NULL, ' \ |
|---|
| 433 | 'out_db boolean NOT NULL, ' \ |
|---|
| 434 | 'overview_factor integer NOT NULL, ' \ |
|---|
| 435 | 'CONSTRAINT raster_overviews_pk ' \ |
|---|
| 436 | 'PRIMARY KEY (o_table_catalog, o_table_schema, o_table_name, o_column, overview_factor));\n' |
|---|
| 437 | |
|---|
| 438 | return sql |
|---|
| 439 | |
|---|
| 440 | |
|---|
| 441 | def make_sql_register_overview(options, ov_table, ov_factor): |
|---|
| 442 | assert len(ov_table) > 0 |
|---|
| 443 | assert ov_factor > 0 |
|---|
| 444 | |
|---|
| 445 | catalog = quote_sql_value('') |
|---|
| 446 | schema = make_sql_schema_table_names(options.table)[0] |
|---|
| 447 | raster_overviews = make_sql_full_table_name(schema + '.raster_overviews') |
|---|
| 448 | r_table = make_sql_table_name(options.table) |
|---|
| 449 | |
|---|
| 450 | sql = "INSERT INTO " + raster_overviews + " ( " \ |
|---|
| 451 | "o_table_catalog, o_table_schema, o_table_name, o_column, " \ |
|---|
| 452 | "r_table_catalog, r_table_schema, r_table_name, r_column, out_db, overview_factor) " \ |
|---|
| 453 | "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', FALSE, %d);\n" % \ |
|---|
| 454 | (catalog, schema, ov_table, options.column, catalog, schema, r_table, options.column, ov_factor) |
|---|
| 455 | |
|---|
| 456 | return sql |
|---|
| 457 | |
|---|
| 458 | def make_sql_vacuum(table): |
|---|
| 459 | sql = 'VACUUM ANALYZE ' + make_sql_full_table_name(table) + ';\n' |
|---|
| 460 | return sql |
|---|
| 461 | |
|---|
| 462 | ################################################################################ |
|---|
| 463 | # RASTER OPERATIONS |
|---|
| 464 | |
|---|
| 465 | def calculate_overviews(ds, band_from = None, band_to = None): |
|---|
| 466 | assert ds is not None |
|---|
| 467 | |
|---|
| 468 | if band_from is None: |
|---|
| 469 | band_from = 0 |
|---|
| 470 | if band_to is None: |
|---|
| 471 | band_to = ds.RasterCount |
|---|
| 472 | |
|---|
| 473 | assert band_to <= ds.RasterCount,'Failed checking band_to=%d <= RasterCount=%d' % (band_to,ds.RasterCount) |
|---|
| 474 | assert band_from <= band_to |
|---|
| 475 | |
|---|
| 476 | nov = 0 |
|---|
| 477 | for i in range(band_from, band_to + 1): |
|---|
| 478 | n = ds.GetRasterBand(i).GetOverviewCount() |
|---|
| 479 | if 0 == nov: |
|---|
| 480 | nov = n |
|---|
| 481 | assert n == nov, 'Number of raster overviews is not the same for all bands' |
|---|
| 482 | |
|---|
| 483 | return nov |
|---|
| 484 | |
|---|
| 485 | def calculate_overview_factor(ds, overview): |
|---|
| 486 | assert ds is not None |
|---|
| 487 | |
|---|
| 488 | |
|---|
| 489 | # Assume all bands have same layout of overviews |
|---|
| 490 | band = ds.GetRasterBand(1) |
|---|
| 491 | assert band is not None |
|---|
| 492 | assert overview < band.GetOverviewCount() |
|---|
| 493 | |
|---|
| 494 | ov_band = band.GetOverview(overview) |
|---|
| 495 | assert ov_band is not None |
|---|
| 496 | |
|---|
| 497 | ovf = int(0.5 + ds.RasterXSize / float(ov_band.XSize)) |
|---|
| 498 | logit('MSG: Overview factor = %d\n' % ovf) |
|---|
| 499 | |
|---|
| 500 | return ovf |
|---|
| 501 | |
|---|
| 502 | |
|---|
| 503 | def collect_pixel_types(ds, band_from, band_to): |
|---|
| 504 | """Collect pixel types of bands in requested range. |
|---|
| 505 | Use names of pixel types in format as returned |
|---|
| 506 | by rt_core function rt_pixtype_name()""" |
|---|
| 507 | |
|---|
| 508 | pt =[] |
|---|
| 509 | for i in range(band_from, band_to): |
|---|
| 510 | band = ds.GetRasterBand(i) |
|---|
| 511 | pixel_type = gdt2pt(band.DataType)['name'][3:] |
|---|
| 512 | pt.append(pixel_type) |
|---|
| 513 | |
|---|
| 514 | return pt |
|---|
| 515 | |
|---|
| 516 | def collect_nodata_values(ds, band_from, band_to): |
|---|
| 517 | """Collect nodata values of bands in requested range""" |
|---|
| 518 | |
|---|
| 519 | nd = [] |
|---|
| 520 | for i in range(band_from, band_to): |
|---|
| 521 | band = ds.GetRasterBand(i) |
|---|
| 522 | nodata = band.GetNoDataValue() |
|---|
| 523 | if nodata is not None: |
|---|
| 524 | nd.append(nodata) |
|---|
| 525 | |
|---|
| 526 | return nd |
|---|
| 527 | |
|---|
| 528 | def calculate_block_size(ds, band_from, band_to): |
|---|
| 529 | """Size of natural block reported by GDAL for bands of given dataset""" |
|---|
| 530 | |
|---|
| 531 | block_dims = None |
|---|
| 532 | for i in range(band_from, band_to): |
|---|
| 533 | band = ds.GetRasterBand(i) |
|---|
| 534 | assert band is not None, "Cannot access raster band %d" % i |
|---|
| 535 | dims = band.GetBlockSize() |
|---|
| 536 | |
|---|
| 537 | # Assume bands with common block size |
|---|
| 538 | if i == band_from: |
|---|
| 539 | block_dims = dims |
|---|
| 540 | |
|---|
| 541 | # Validate dimensions of bands block |
|---|
| 542 | if block_dims != dims: |
|---|
| 543 | logit("MSG: Block sizes don't match: %s != %s\n" % (str(block_dims), str(dims))) |
|---|
| 544 | |
|---|
| 545 | assert block_dims is not None, "Failed to calculate block size" |
|---|
| 546 | return (int(block_dims[0]), int(block_dims[1])) |
|---|
| 547 | |
|---|
| 548 | def calculate_grid_size(raster_size, block_size): |
|---|
| 549 | """Dimensions of grid made up with blocks of requested size""" |
|---|
| 550 | |
|---|
| 551 | # Exact number of grid dimensions |
|---|
| 552 | nx = float(raster_size[0]) / float(block_size[0]) |
|---|
| 553 | ny = float(raster_size[1]) / float(block_size[1]) |
|---|
| 554 | |
|---|
| 555 | return ( int(math.ceil(nx)), int(math.ceil(ny))) |
|---|
| 556 | |
|---|
| 557 | def calculate_block_pad_size(band, xoff, yoff, block_size): |
|---|
| 558 | """Calculates number of columns [0] and rows [1] of padding""" |
|---|
| 559 | assert band is not None |
|---|
| 560 | |
|---|
| 561 | xpad = 0 |
|---|
| 562 | ypad= 0 |
|---|
| 563 | block_bound = ( xoff + block_size[0], yoff + block_size[1] ) |
|---|
| 564 | |
|---|
| 565 | if block_bound[0] > band.XSize: |
|---|
| 566 | xpad = block_bound[0] - band.XSize |
|---|
| 567 | if block_bound[1] > band.YSize: |
|---|
| 568 | ypad = block_bound[1] - band.YSize |
|---|
| 569 | |
|---|
| 570 | return (xpad, ypad) |
|---|
| 571 | |
|---|
| 572 | def get_gdal_geotransform(ds): |
|---|
| 573 | assert ds is not None |
|---|
| 574 | gt = list(ds.GetGeoTransform()) |
|---|
| 575 | return tuple(gt) |
|---|
| 576 | |
|---|
| 577 | def calculate_geoxy(gt, xy): |
|---|
| 578 | """Calculate georeferenced coordinate from given x and y""" |
|---|
| 579 | assert gt is not None |
|---|
| 580 | assert xy is not None |
|---|
| 581 | assert len(xy) == 2 |
|---|
| 582 | |
|---|
| 583 | xgeo = gt[0] + gt[1] * xy[0] + gt[2] * xy[1]; |
|---|
| 584 | ygeo = gt[3] + gt[4] * xy[0] + gt[5] * xy[1]; |
|---|
| 585 | |
|---|
| 586 | return (xgeo, ygeo) |
|---|
| 587 | |
|---|
| 588 | def calculate_geoxy_level(gt, xy, level): |
|---|
| 589 | |
|---|
| 590 | # Update pixel resolution according to overview level |
|---|
| 591 | newgt = ( gt[0], gt[1] * float(level), gt[2], gt[3], gt[4], gt[5] * float(level) ) |
|---|
| 592 | |
|---|
| 593 | return calculate_geoxy(newgt, xy) |
|---|
| 594 | |
|---|
| 595 | def calculate_bounding_box(ds, gt): |
|---|
| 596 | """Calculate georeferenced coordinates of spatial extent of raster dataset""" |
|---|
| 597 | assert ds is not None |
|---|
| 598 | |
|---|
| 599 | # UL, LL, UR, LR |
|---|
| 600 | dim = ( (0,0),(0,ds.RasterYSize),(ds.RasterXSize,0),(ds.RasterXSize,ds.RasterYSize) ) |
|---|
| 601 | |
|---|
| 602 | ext = (calculate_geoxy(gt, dim[0]), calculate_geoxy(gt, dim[1]), |
|---|
| 603 | calculate_geoxy(gt, dim[2]), calculate_geoxy(gt, dim[3])) |
|---|
| 604 | |
|---|
| 605 | return ext |
|---|
| 606 | |
|---|
| 607 | def check_hex(hex, bytes_size = None): |
|---|
| 608 | assert hex is not None, "Error: Missing hex string" |
|---|
| 609 | size = len(hex) |
|---|
| 610 | assert size > 0, "Error: hex string is empty" |
|---|
| 611 | assert size % 2 == 0, "Error: invalid size of hex string" |
|---|
| 612 | if bytes_size is not None: |
|---|
| 613 | n = int(size / 2) |
|---|
| 614 | assert n == bytes_size, "Error: invalid number of bytes %d, expected %d" % (n, bytes_size) |
|---|
| 615 | |
|---|
| 616 | def dump_block_numpy(pixels): |
|---|
| 617 | assert pixels.ndim == 2 |
|---|
| 618 | print 'BEGIN BLOCK SCANLINES (numpy): (%d, %d)' %(len(pixels[0]), len(pixels)) |
|---|
| 619 | |
|---|
| 620 | i = 0 |
|---|
| 621 | for row in range (0, len(pixels)): |
|---|
| 622 | s = binascii.hexlify(pixels[row]) |
|---|
| 623 | print '%d (%d)\t%s' % (i, (len(s) / 2), s) |
|---|
| 624 | i = i + 1 |
|---|
| 625 | |
|---|
| 626 | print 'END BLOCK SCANLINES' |
|---|
| 627 | |
|---|
| 628 | def fetch_band_nodata(band, default = 0): |
|---|
| 629 | assert band is not None |
|---|
| 630 | |
|---|
| 631 | nodata = default |
|---|
| 632 | if band.GetNoDataValue() is not None: |
|---|
| 633 | nodata = band.GetNoDataValue() |
|---|
| 634 | else: |
|---|
| 635 | logit("WARNING: No NODATA flagged in raster_columns metadata. " |
|---|
| 636 | "In serialized raster, NODATA bytes will have value of 0.\n") |
|---|
| 637 | return nodata |
|---|
| 638 | |
|---|
| 639 | def wkblify(fmt, data): |
|---|
| 640 | """Writes raw binary data into HEX-encoded string using binascii module.""" |
|---|
| 641 | import struct |
|---|
| 642 | |
|---|
| 643 | # Binary to HEX |
|---|
| 644 | fmt_little = '<' +fmt |
|---|
| 645 | hexstr = binascii.hexlify(struct.pack(fmt_little, data)).upper() |
|---|
| 646 | |
|---|
| 647 | # String'ify raw value for log |
|---|
| 648 | valfmt = '\'' + fmt2printfmt(fmt[len(fmt) - 1]) + '\'' |
|---|
| 649 | val = valfmt % data |
|---|
| 650 | logit('HEX (\'fmt=%s\', bytes=%d, val=%s):\t\t%s\n' \ |
|---|
| 651 | % (fmt, len(hexstr) / 2, str(val), hexstr)) |
|---|
| 652 | |
|---|
| 653 | return hexstr |
|---|
| 654 | |
|---|
| 655 | def wkblify_raster_header(options, ds, level, ulp, xsize = None, ysize = None): |
|---|
| 656 | """Writes WKT Raster header based on given GDAL into HEX-encoded WKB.""" |
|---|
| 657 | assert ds is not None, "Error: Missing GDAL dataset" |
|---|
| 658 | assert level >= 1 |
|---|
| 659 | assert len(ulp) == 2 is not None, "Error: invalid upper-left corner" |
|---|
| 660 | |
|---|
| 661 | if xsize is None or ysize is None: |
|---|
| 662 | assert xsize is None and ysize is None |
|---|
| 663 | xsize = ds.RasterXSize |
|---|
| 664 | ysize = ds.RasterYSize |
|---|
| 665 | |
|---|
| 666 | # Collect GeoReference information |
|---|
| 667 | gt = get_gdal_geotransform(ds) |
|---|
| 668 | ul = calculate_geoxy(gt, (ulp[0], ulp[1])) |
|---|
| 669 | rt_ip = ( ul[0], ul[1] ) |
|---|
| 670 | rt_skew = ( gt[2], gt[4] ) |
|---|
| 671 | rt_scale = ( gt[1] * level, gt[5] * level ) |
|---|
| 672 | |
|---|
| 673 | # TODO: Any way to lookup for SRID based on SRS in WKT? |
|---|
| 674 | #srs = osr.SpatialReference() |
|---|
| 675 | #srs.ImportFromWkt(ds.GetProjection()) |
|---|
| 676 | |
|---|
| 677 | # Burn input raster as WKTRaster WKB format |
|---|
| 678 | hexwkb = '' |
|---|
| 679 | ### Endiannes |
|---|
| 680 | hexwkb += wkblify('B', options.endian) |
|---|
| 681 | ### Version |
|---|
| 682 | hexwkb += wkblify('H', options.version) |
|---|
| 683 | ### Number of bands |
|---|
| 684 | hexwkb += wkblify('H', ds.RasterCount) |
|---|
| 685 | check_hex(hexwkb, 5) |
|---|
| 686 | ### Georeference |
|---|
| 687 | hexwkb += wkblify('d', rt_scale[0]) |
|---|
| 688 | hexwkb += wkblify('d', rt_scale[1]) |
|---|
| 689 | hexwkb += wkblify('d', rt_ip[0]) |
|---|
| 690 | hexwkb += wkblify('d', rt_ip[1]) |
|---|
| 691 | hexwkb += wkblify('d', rt_skew[0]) |
|---|
| 692 | hexwkb += wkblify('d', rt_skew[1]) |
|---|
| 693 | hexwkb += wkblify('i', options.srid) |
|---|
| 694 | check_hex(hexwkb, 57) |
|---|
| 695 | ### Number of columns and rows |
|---|
| 696 | hexwkb += wkblify('H', xsize) |
|---|
| 697 | hexwkb += wkblify('H', ysize) |
|---|
| 698 | check_hex(hexwkb, 61) |
|---|
| 699 | |
|---|
| 700 | logit("MSG: Georeference: px = %s -> ul = %s \tresolution = %s \trotation = %s\n" \ |
|---|
| 701 | % (str(ulp), str(rt_ip), str(rt_scale), str(rt_skew))) |
|---|
| 702 | return hexwkb |
|---|
| 703 | |
|---|
| 704 | def wkblify_band_header(options, band): |
|---|
| 705 | """Writes band header into HEX-encoded WKB""" |
|---|
| 706 | assert band is not None, "Error: Missing GDAL raster band" |
|---|
| 707 | |
|---|
| 708 | hexwkb = "" |
|---|
| 709 | |
|---|
| 710 | first4bits = 0 |
|---|
| 711 | |
|---|
| 712 | # If the register option is enabled, set the first bit to 1 |
|---|
| 713 | if options.register: |
|---|
| 714 | first4bits = 128 |
|---|
| 715 | |
|---|
| 716 | nodata = band.GetNoDataValue() |
|---|
| 717 | # If there is no nodata value, set it to 0. Otherwise set the HasNodata bit to 1 |
|---|
| 718 | if nodata is not None: |
|---|
| 719 | first4bits += 64 |
|---|
| 720 | else: |
|---|
| 721 | nodata = 0 |
|---|
| 722 | |
|---|
| 723 | # Encode pixel type |
|---|
| 724 | pixtype = gdt2pt(band.DataType)['id'] |
|---|
| 725 | hexwkb += wkblify('B', pixtype + first4bits) |
|---|
| 726 | |
|---|
| 727 | # Encode NODATA value (or Zero, if NODATA unavailable) |
|---|
| 728 | hexwkb += wkblify(pt2fmt(pixtype), nodata) |
|---|
| 729 | |
|---|
| 730 | check_hex(hexwkb) |
|---|
| 731 | return hexwkb |
|---|
| 732 | |
|---|
| 733 | def wkblify_band(options, band, level, xoff, yoff, read_block_size, block_size, infile, bandidx): |
|---|
| 734 | """Writes band of given GDAL dataset into HEX-encoded WKB for WKT Raster output.""" |
|---|
| 735 | assert band is not None, "Error: Missing GDAL raster band" |
|---|
| 736 | |
|---|
| 737 | hexwkb = '' |
|---|
| 738 | |
|---|
| 739 | if options.register: |
|---|
| 740 | # Off-db raster |
|---|
| 741 | # TODO: Do we want to handle options.overview_level? --mloskot |
|---|
| 742 | # ANSWER: |
|---|
| 743 | # TODO: Where bandidx and ds come from? --mloskot |
|---|
| 744 | # ANSWER: Provided by caller method --jorgearevalo |
|---|
| 745 | hexwkb += wkblify('B', bandidx - 1) |
|---|
| 746 | filepath = os.path.abspath(infile.replace('\\', '\\\\')) |
|---|
| 747 | logit('MSG: Out-db raster path=%s\n' % filepath) |
|---|
| 748 | hexwkb += wkblify(str(len(filepath)) + 's', filepath) |
|---|
| 749 | hexwkb += wkblify('B', 0) |
|---|
| 750 | else: |
|---|
| 751 | # In-db raster |
|---|
| 752 | |
|---|
| 753 | # Right most column and bottom most row of blocks have |
|---|
| 754 | # portions that extend beyond the raster |
|---|
| 755 | read_padding_size = calculate_block_pad_size(band, xoff, yoff, read_block_size) |
|---|
| 756 | valid_read_block_size = ( read_block_size[0] - read_padding_size[0], |
|---|
| 757 | read_block_size[1] - read_padding_size[1] ) |
|---|
| 758 | |
|---|
| 759 | |
|---|
| 760 | if read_padding_size[0] > 0 or read_padding_size[1] > 0: |
|---|
| 761 | target_block_size = (valid_read_block_size[0] / level, valid_read_block_size[1] / level) |
|---|
| 762 | target_padding_size = (read_padding_size[0] / level, read_padding_size[1] / level) |
|---|
| 763 | else: |
|---|
| 764 | target_block_size = block_size |
|---|
| 765 | target_padding_size = ( 0, 0 ) |
|---|
| 766 | |
|---|
| 767 | logit('MSG: Normalize read_block=%s for level=%d to valid_read_block=%s with padding=%s\n' % \ |
|---|
| 768 | (read_block_size, level, valid_read_block_size, read_padding_size)) |
|---|
| 769 | logit('MSG: Normalize target_block=%s for level=%d to valid_target_block=%s with padding=%s\n' % \ |
|---|
| 770 | (block_size, level, target_block_size, target_padding_size)) |
|---|
| 771 | logit('MSG: ReadAsArray( %d, %d, %s, %s)\n' % \ |
|---|
| 772 | (xoff, yoff, str(valid_read_block_size), str(target_block_size))) |
|---|
| 773 | |
|---|
| 774 | assert valid_read_block_size[0] > 0 and valid_read_block_size[1] > 0 |
|---|
| 775 | assert target_block_size[0] > 0 and target_block_size[1] > 0 |
|---|
| 776 | |
|---|
| 777 | pixels = band.ReadAsArray(xoff, yoff, valid_read_block_size[0], valid_read_block_size[1], |
|---|
| 778 | target_block_size[0], target_block_size[1]) |
|---|
| 779 | |
|---|
| 780 | # XXX: Use for debugging only |
|---|
| 781 | #dump_block_numpy(pixels) |
|---|
| 782 | |
|---|
| 783 | out_pixels = numpy.zeros((block_size[1], block_size[0]), pt2numpy(band.DataType)) |
|---|
| 784 | |
|---|
| 785 | logit('MSG: Read valid source:\t%d x %d\n' % (len(pixels[0]), len(pixels))) |
|---|
| 786 | logit('MSG: Write into block:\t%d x %d\n' % (len(out_pixels[0]), len(out_pixels))) |
|---|
| 787 | |
|---|
| 788 | if target_padding_size[0] > 0 or target_padding_size[1] > 0: |
|---|
| 789 | |
|---|
| 790 | ysize_read_pixels = len(pixels) |
|---|
| 791 | nodata_value = fetch_band_nodata(band) |
|---|
| 792 | |
|---|
| 793 | # Apply columns padding |
|---|
| 794 | pad_cols = numpy.array([nodata_value] * target_padding_size[0]) |
|---|
| 795 | for row in range (0, ysize_read_pixels): |
|---|
| 796 | out_line = numpy.append(pixels[row], pad_cols) |
|---|
| 797 | out_pixels[row] = out_line |
|---|
| 798 | |
|---|
| 799 | # Fill rows padding with nodata value |
|---|
| 800 | for row in range(ysize_read_pixels, ysize_read_pixels + target_padding_size[1]): |
|---|
| 801 | out_pixels[row].fill(nodata_value) |
|---|
| 802 | else: |
|---|
| 803 | out_pixels = pixels |
|---|
| 804 | |
|---|
| 805 | # XXX: Use for debugging only |
|---|
| 806 | #dump_block_numpy(out_pixels) |
|---|
| 807 | |
|---|
| 808 | hexwkb = binascii.hexlify(out_pixels) |
|---|
| 809 | |
|---|
| 810 | check_hex(hexwkb) |
|---|
| 811 | return hexwkb |
|---|
| 812 | |
|---|
| 813 | def wkblify_raster_level(options, ds, level, band_range, infile, i): |
|---|
| 814 | assert ds is not None |
|---|
| 815 | assert level >= 1 |
|---|
| 816 | assert len(band_range) == 2 |
|---|
| 817 | |
|---|
| 818 | band_from = band_range[0] |
|---|
| 819 | band_to = band_range[1] |
|---|
| 820 | |
|---|
| 821 | # Collect raster and block dimensions |
|---|
| 822 | raster_size = ( ds.RasterXSize, ds.RasterYSize ) |
|---|
| 823 | if options.block_size is not None: |
|---|
| 824 | block_size = parse_block_size(options) |
|---|
| 825 | read_block_size = ( block_size[0] * level, block_size[1] * level) |
|---|
| 826 | grid_size = calculate_grid_size(raster_size, read_block_size) |
|---|
| 827 | else: |
|---|
| 828 | block_size = raster_size # Whole raster as a single block |
|---|
| 829 | read_block_size = block_size |
|---|
| 830 | grid_size = (1, 1) |
|---|
| 831 | |
|---|
| 832 | logit("MSG: Processing raster=%s using read_block_size=%s block_size=%s of grid=%s in level=%d\n" % \ |
|---|
| 833 | (str(raster_size), str(read_block_size), str(block_size), str(grid_size), level)) |
|---|
| 834 | |
|---|
| 835 | # Register base raster in RASTER_COLUMNS - SELECT AddRasterColumn(); |
|---|
| 836 | if level == 1: |
|---|
| 837 | if i == 0: |
|---|
| 838 | gt = get_gdal_geotransform(ds) |
|---|
| 839 | pixel_size = ( gt[1], gt[5] ) |
|---|
| 840 | pixel_types = collect_pixel_types(ds, band_from, band_to) |
|---|
| 841 | nodata_values = collect_nodata_values(ds, band_from, band_to) |
|---|
| 842 | extent = calculate_bounding_box(ds, gt) |
|---|
| 843 | sql = make_sql_addrastercolumn(options, pixel_types, nodata_values, |
|---|
| 844 | pixel_size, block_size, extent) |
|---|
| 845 | options.output.write(sql) |
|---|
| 846 | gen_table = options.table |
|---|
| 847 | |
|---|
| 848 | else: |
|---|
| 849 | assert level > 1 |
|---|
| 850 | # Create overview table and register in RASTER_OVERVIEWS |
|---|
| 851 | |
|---|
| 852 | # CREATE TABLE o_<LEVEL>_<NAME> ( rid serial, options.column RASTER ) |
|---|
| 853 | schema_table_names = make_sql_schema_table_names(options.table) |
|---|
| 854 | level_table_name = 'o_' + str(level) + '_' + schema_table_names[1] |
|---|
| 855 | level_table = schema_table_names[0] + '.' + level_table_name |
|---|
| 856 | if i == 0: |
|---|
| 857 | sql = make_sql_create_table(options, level_table, True) |
|---|
| 858 | options.output.write(sql) |
|---|
| 859 | sql = make_sql_register_overview(options, level_table_name, level) |
|---|
| 860 | options.output.write(sql) |
|---|
| 861 | gen_table = level_table |
|---|
| 862 | |
|---|
| 863 | # Write (original) raster to hex binary output |
|---|
| 864 | tile_count = 0 |
|---|
| 865 | hexwkb = '' |
|---|
| 866 | |
|---|
| 867 | for ycell in range(0, grid_size[1]): |
|---|
| 868 | for xcell in range(0, grid_size[0]): |
|---|
| 869 | |
|---|
| 870 | xoff = xcell * read_block_size[0] |
|---|
| 871 | yoff = ycell * read_block_size[1] |
|---|
| 872 | |
|---|
| 873 | logit("MSG: --------- CELL #%04d\tindex = %d x %d\tdim = (%d x %d)\t(%d x %d) \t---------\n" % \ |
|---|
| 874 | (tile_count, xcell, ycell, xoff, yoff, xoff + read_block_size[0], yoff + read_block_size[1])) |
|---|
| 875 | |
|---|
| 876 | if options.block_size is not None: |
|---|
| 877 | hexwkb = '' # Reset buffer as single INSERT per tile is generated |
|---|
| 878 | hexwkb += wkblify_raster_header(options, ds, level, (xoff, yoff), |
|---|
| 879 | block_size[0], block_size[1]) |
|---|
| 880 | else: |
|---|
| 881 | hexwkb += wkblify_raster_header(options, ds, level, (xoff, yoff)) |
|---|
| 882 | |
|---|
| 883 | for b in range(band_from, band_to): |
|---|
| 884 | band = ds.GetRasterBand(b) |
|---|
| 885 | assert band is not None, "Missing GDAL raster band %d" % b |
|---|
| 886 | logit("MSG: Band %d\n" % b) |
|---|
| 887 | |
|---|
| 888 | hexwkb += wkblify_band_header(options, band) |
|---|
| 889 | hexwkb += wkblify_band(options, band, level, xoff, yoff, read_block_size, block_size, infile, b) |
|---|
| 890 | |
|---|
| 891 | # INSERT INTO |
|---|
| 892 | check_hex(hexwkb) # TODO: Remove to not to decrease performance |
|---|
| 893 | sql = make_sql_insert_raster(gen_table, options.column, hexwkb, options.filename, file) |
|---|
| 894 | options.output.write(sql) |
|---|
| 895 | tile_count = tile_count + 1 |
|---|
| 896 | |
|---|
| 897 | return (gen_table, tile_count) |
|---|
| 898 | |
|---|
| 899 | def wkblify_raster(options, infile, i): |
|---|
| 900 | """Writes given raster dataset using GDAL features into HEX-encoded of |
|---|
| 901 | WKB for WKT Raster output.""" |
|---|
| 902 | |
|---|
| 903 | assert infile is not None, "Input file is none, expected file name" |
|---|
| 904 | assert options.version == g_rt_version, "Error: invalid WKT Raster protocol version" |
|---|
| 905 | assert options.endian == NDR, "Error: invalid endianness, use little-endian (NDR) only" |
|---|
| 906 | assert options.srid >= -1, "Error: do you really want to specify SRID = %d" % options.srid |
|---|
| 907 | |
|---|
| 908 | # Open source raster file |
|---|
| 909 | ds = gdal.Open(infile, gdalc.GA_ReadOnly); |
|---|
| 910 | if ds is None: |
|---|
| 911 | sys.exit('Error: Cannot open input file: ' + str(infile)) |
|---|
| 912 | |
|---|
| 913 | # By default, translate all raster bands |
|---|
| 914 | |
|---|
| 915 | # Calculate range for single-band request |
|---|
| 916 | if options.band is not None and options.band > 0: |
|---|
| 917 | band_range = ( options.band, options.band + 1 ) |
|---|
| 918 | else: |
|---|
| 919 | band_range = ( 1, ds.RasterCount + 1 ) |
|---|
| 920 | |
|---|
| 921 | # Generate requested overview level (base raster if level = 1) |
|---|
| 922 | summary = wkblify_raster_level(options, ds, options.overview_level, band_range, infile, i) |
|---|
| 923 | SUMMARY.append( summary ) |
|---|
| 924 | |
|---|
| 925 | # Cleanup |
|---|
| 926 | ds = None |
|---|
| 927 | |
|---|
| 928 | ################################################################################ |
|---|
| 929 | |
|---|
| 930 | def main(): |
|---|
| 931 | |
|---|
| 932 | (opts, args) = parse_command_line() |
|---|
| 933 | |
|---|
| 934 | global VERBOSE |
|---|
| 935 | VERBOSE = opts.verbose |
|---|
| 936 | |
|---|
| 937 | global SUMMARY |
|---|
| 938 | SUMMARY = [] |
|---|
| 939 | |
|---|
| 940 | saved_out = sys.stdout |
|---|
| 941 | if type(opts.output) is str: |
|---|
| 942 | filename = opts.output |
|---|
| 943 | opts.output = open(filename, "w") |
|---|
| 944 | |
|---|
| 945 | # BEGIN |
|---|
| 946 | opts.output.write('BEGIN;\n') |
|---|
| 947 | |
|---|
| 948 | # If overviews requested, CREATE TABLE raster_overviews |
|---|
| 949 | if opts.create_raster_overviews_table: |
|---|
| 950 | sql = make_sql_create_raster_overviews(opts) |
|---|
| 951 | opts.output.write(sql) |
|---|
| 952 | |
|---|
| 953 | # Base raster schema |
|---|
| 954 | if opts.overview_level == 1: |
|---|
| 955 | # DROP TABLE |
|---|
| 956 | if opts.drop_table: |
|---|
| 957 | sql = make_sql_drop_raster_table(opts.table) |
|---|
| 958 | opts.output.write(sql) |
|---|
| 959 | |
|---|
| 960 | # CREATE TABLE |
|---|
| 961 | if opts.create_table and opts.overview_level == 1: |
|---|
| 962 | sql = make_sql_create_table(opts) |
|---|
| 963 | opts.output.write(sql) |
|---|
| 964 | |
|---|
| 965 | # INSERT |
|---|
| 966 | i = 0 |
|---|
| 967 | |
|---|
| 968 | # Burn all specified input raster files into single WKTRaster table |
|---|
| 969 | for infile in opts.raster: |
|---|
| 970 | filelist = glob.glob(infile) |
|---|
| 971 | assert len(filelist) > 0, "No input raster files found for '" + str(infile) + "'" |
|---|
| 972 | |
|---|
| 973 | for filename in filelist: |
|---|
| 974 | logit("MSG: Dataset #%d: %s\n" % (i + 1, filename)) |
|---|
| 975 | |
|---|
| 976 | # Write raster data to WKB and send it to opts.output |
|---|
| 977 | wkblify_raster(opts, filename, i) |
|---|
| 978 | i += 1 |
|---|
| 979 | |
|---|
| 980 | # INDEX |
|---|
| 981 | if opts.index and SUMMARY is not None: |
|---|
| 982 | sql = make_sql_create_gist(SUMMARY[0][0], opts.column) |
|---|
| 983 | opts.output.write(sql) |
|---|
| 984 | |
|---|
| 985 | # COMMIT |
|---|
| 986 | opts.output.write('END;\n') |
|---|
| 987 | |
|---|
| 988 | # VACUUM |
|---|
| 989 | if opts.vacuum and SUMMARY is not None: |
|---|
| 990 | sql = make_sql_vacuum(SUMMARY[0][0]) |
|---|
| 991 | opts.output.write(sql) |
|---|
| 992 | |
|---|
| 993 | # Cleanup |
|---|
| 994 | if opts.output != sys.stdout: |
|---|
| 995 | sys.stdout = saved_out |
|---|
| 996 | |
|---|
| 997 | print "------------------------------------------------------------" |
|---|
| 998 | print " Summary of GDAL to WKT Raster processing:" |
|---|
| 999 | print "------------------------------------------------------------" |
|---|
| 1000 | if i == 1: |
|---|
| 1001 | m = '%d (%s)' % (i, infile) |
|---|
| 1002 | else: |
|---|
| 1003 | m = '%d' % i |
|---|
| 1004 | print "Number of processed raster files: " + m |
|---|
| 1005 | print "List of generated tables (number of tiles):" |
|---|
| 1006 | i = 0 |
|---|
| 1007 | for s in SUMMARY: |
|---|
| 1008 | i += 1 |
|---|
| 1009 | print "%d\t%s (%d)" % (i, s[0], s[1]) |
|---|
| 1010 | |
|---|
| 1011 | ################################################################################ |
|---|
| 1012 | |
|---|
| 1013 | if __name__ == "__main__": |
|---|
| 1014 | main() |
|---|