#!/usr/bin/env python
### Copyright (C) 2008-2012 Antonio Valentino <a_valentino@users.sf.net>
### This file is part of GSDView.
### GSDView is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation; either version 2 of the License, or
### (at your option) any later version.
### GSDView is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
### You should have received a copy of the GNU General Public License
### along with GSDView; if not, write to the Free Software
### Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
'''Generate a vector file containing the bounding box of raster data.
Take in input one or more raster datasets and generate a vector file
containing the bounding box polygon and, optionally, a GCP layer of
each input dataset.
Note: currently only the KML format is supported for output.
This program aims to be an improved and more flexible version of the
gdaltindex utility.
.. seealso: http://www.gdal.org/gdaltindex.html
'''
# @TODO:
#
# * support more output formats
# * confogurable spatial reference system
# * configurable database field for path storage (see gdaltindex)
# * colors
# * filling (with transparency)
import os
import sys
import logging
from osgeo import gdal, ogr, osr
__author__ = 'Antonio Valentino <a_valentino@users.sf.net>'
__date__ = '$Date$'
__revision__ = '$Revision$'
__version__ = '1.0'
if hasattr(os, 'EX_USAGE'):
EX_USAGE = os.EX_USAGE
else:
EX_USAGE = 64
#DEFAULT_OGRDRIVER = 'KML' # compatibility with old GDAL versions
DEFAULT_OGRDRIVER = 'LIBKML'
[docs]def makesrs(srs):
# @NOTE: KML by specification uses only a single projection, EPSG:4326
# (a.k.a. WGS84)
if srs is None:
srs = osr.SpatialReference()
srs.SetWellKnownGeogCS('EPSG:4326')
elif isinstance(srs, basestring):
spec = srs
srs = osr.SpatialReference()
srs.SetFromUserInput(spec)
return srs
[docs]def create_box_layer(ds, name='', srs=None, gtype=ogr.wkbUnknown, opt=None):
srs = makesrs(srs)
if opt == None:
opt = ''
layer = ds.CreateLayer(name, srs, gtype, opt)
if layer is None:
raise RuntimeError('layer creation failed.')
field = ogr.FieldDefn('Name', ogr.OFTString)
layer.CreateField(field)
field = ogr.FieldDefn('Description', ogr.OFTString)
layer.CreateField(field)
return layer
[docs]def create_GCP_layer(ds, name='', srs=None, gtype=ogr.wkbPoint25D, opt=None):
srs = makesrs(srs)
if opt == None:
opt = ''
layer = ds.CreateLayer(name, srs, gtype, opt)
if layer is None:
raise RuntimeError('layer creation failed.')
field = ogr.FieldDefn('Name', ogr.OFTString)
layer.CreateField(field)
field = ogr.FieldDefn('Description', ogr.OFTString)
layer.CreateField(field)
#field = ogr.FieldDefn('X', ogr.OFTReal)
#layer.CreateField(field)
#
#field = ogr.FieldDefn('Y', ogr.OFTReal)
#layer.CreateField(field)
#
#field = ogr.FieldDefn('Z', ogr.OFTReal)
#layer.CreateField(field)
field = ogr.FieldDefn('Pixel', ogr.OFTReal)
layer.CreateField(field)
field = ogr.FieldDefn('Line', ogr.OFTReal)
layer.CreateField(field)
field = ogr.FieldDefn('Info', ogr.OFTString)
layer.CreateField(field)
field = ogr.FieldDefn('Id', ogr.OFTString)
layer.CreateField(field)
return layer
[docs]def geographic_info(ds, srsout=None):
# Read geotransform matrix and source reference system
srs = osr.SpatialReference()
if ds.GetGCPCount():
gcps = ds.GetGCPs()
srs.ImportFromWkt(ds.GetGCPProjection())
geomatrix = gdal.GCPsToGeoTransform(gcps)
else:
gcps = []
if not ds.GetProjection():
raise ValueError('no geographic info in "%s"' %
ds.GetDescription())
srs.ImportFromWkt(ds.GetProjection())
geomatrix = ds.GetGeoTransform()
if geomatrix == (0.0, 1.0, 0.0, 0.0, 0.0, 1.0):
raise ValueError('no geographic info in "%s"' % ds.GetDescription())
# Set the target reference system
srsout = makesrs(srsout)
# Instantiate the coordinate transformer
transformer = osr.CoordinateTransformation(srs, srsout)
# dataset corners setup
corners = []
for id_, (pixel, line) in enumerate((
(0, 0),
(0, ds.RasterYSize - 1),
(ds.RasterXSize - 1, ds.RasterYSize - 1),
(ds.RasterXSize - 1, 0))):
X = geomatrix[0] + geomatrix[1] * pixel + geomatrix[2] * line
Y = geomatrix[3] + geomatrix[4] * pixel + geomatrix[5] * line
# Shift to the center of the pixel
X += geomatrix[1] / 2.0
Y += geomatrix[5] / 2.0
Z = 0.
(x, y, z) = transformer.TransformPoint(X, Y, Z)
gcp = gdal.GCP(x, y, z, pixel, line, '', str(id_ + 1))
corners.append(gcp)
# convert GCPs to the targer srs
outgcps = []
for gcp in gcps:
(x, y, z) = transformer.TransformPoint(gcp.GCPX, gcp.GCPY, gcp.GCPZ)
gcp = gdal.GCP(x, y, z, gcp.GCPPixel, gcp.GCPLine, gcp.Info, gcp.Id)
outgcps.append(gcp)
return corners, outgcps
[docs]def export_bounding_box(layer, corners, description='', mark_corners=True):
# ring
ring = ogr.Geometry(type=ogr.wkbLinearRing)
for gcp in corners:
ring.AddPoint_2D(gcp.GCPX, gcp.GCPY)
ring.CloseRings()
# polygon
poly = ogr.Geometry(type=ogr.wkbPolygon)
poly.AddGeometry(ring)
# feature
featurename = 'bounding box'
if description and os.path.basename(description) not in layer.GetName():
featurename = os.path.basename(description)
feature = ogr.Feature(layer.GetLayerDefn())
feature.SetField('Name', featurename)
feature.SetField('Description', description)
feature.SetStyleString('BRUSH(fc:#FF000064);PEN(c:#FF0000)') # red filled
feature.SetGeometry(poly)
if layer.CreateFeature(feature) != 0:
raise RuntimeError('failed to create a new feature.')
feature.Destroy()
if mark_corners:
for gcp in corners:
point = ogr.Geometry(type=ogr.wkbPoint)
point.SetPoint_2D(0, gcp.GCPX, gcp.GCPY)
line, pixel = gcp.GCPLine, gcp.GCPPixel
feature = ogr.Feature(layer.GetLayerDefn())
feature.SetField('Name', '(%.1f, %.1f)' % (line, pixel))
feature.SetField('Description',
'row = %.1f\ncol = %.1f' % (line, pixel))
feature.SetStyleString('SYMBOL(c:#FF0000)') # red
feature.SetGeometry(point)
if layer.CreateFeature(feature) != 0:
raise RuntimeError('failed to create a new feature.')
feature.Destroy()
[docs]def export_gcps(layer, gcps):
for id_, gcp in enumerate(gcps):
if gcp.Id:
id_ = gcp.Id
point = ogr.Geometry(type=ogr.wkbPoint25D)
point.SetPoint(0, gcp.GCPX, gcp.GCPY, gcp.GCPZ)
feature = ogr.Feature(layer.GetLayerDefn())
feature.SetField('Name', 'GCPs %s' % id_)
feature.SetField('Description', gcp.Info)
#feature.SetField('X', gcp.GCPX)
#feature.SetField('Y', gcp.GCPY)
#feature.SetField('Z', gcp.GCPZ)
feature.SetField('Pixel', gcp.GCPPixel)
feature.SetField('Line', gcp.GCPLine)
feature.SetField('Info', gcp.Info)
feature.SetField('Id', gcp.Id)
feature.SetGeometry(point)
if layer.CreateFeature(feature) != 0:
raise RuntimeError('failed to create a new feature.')
feature.Destroy()
[docs]def create_datasource(filename, drivername=None):
if not drivername:
drivername = DEFAULT_OGRDRIVER
driver = ogr.GetDriverByName(drivername)
if not driver:
raise RuntimeError('unable to instantiate the "%s" driver.' %
drivername)
else:
logging.info('using "%s" driver.' % drivername)
ds = driver.CreateDataSource(filename)
if ds is None:
raise RuntimeError('creation of output file ("%s") failed.' % filename)
logging.info('output file: "%s".' % filename)
return ds
[docs]def export_raster(src, dst, boxlayer=None, gcplayer=None, srsout=None,
mark_corners=True):
if isinstance(src, basestring):
filename = src
src = gdal.Open(filename)
if src is None:
raise RuntimeError('unable to open source dataset: "%s"' %
filename)
if isinstance(dst, basestring):
dst = create_datasource(dst)
# Set the target reference system
srsout = makesrs(srsout)
# Instantiate the coordinate transformer
corners, gcps = geographic_info(src, srsout)
# Bounding box
description = src.GetDescription().strip()
if boxlayer is None or boxlayer == '':
boxlayername = os.path.basename(description)
boxlayer = create_box_layer(dst, boxlayername, srsout)
elif boxlayer and isinstance(boxlayer, basestring):
boxlayername = boxlayer
boxlayer = dst.GetLayerByName(boxlayername)
if boxlayer is None:
boxlayer = create_box_layer(dst, boxlayername, srsout)
if boxlayer is None:
raise RuntimeError('unable to create a new layer.')
export_bounding_box(boxlayer, corners, description, mark_corners)
# GCPs
if gcps and gcplayer is not False:
if gcplayer in (None, True, ''):
gcplayername = 'gcps_%s' % os.path.basename(description)
gcplayer = create_GCP_layer(dst, gcplayername, srsout)
elif gcplayer and isinstance(gcplayer, basestring):
gcplayername = gcplayer
gcplayer = dst.GetLayerByName(gcplayername)
if gcplayer is None:
gcplayer = create_GCP_layer(dst, gcplayername, srsout)
if gcplayer is None:
raise RuntimeError('unable to create a new layer.')
export_gcps(gcplayer, gcps)
return boxlayer, gcplayer
[docs]def compact_index(srclist, dst):
if isinstance(dst, basestring):
dst = create_datasource(dst)
# Bounding box
boxlayername = 'index'
boxlayer = dst.GetLayerByName(boxlayername)
if boxlayer is None:
boxlayer = create_box_layer(dst, boxlayername)
for src in srclist:
logging.info('adding "%s"' % src)
if isinstance(src, basestring):
filename = src
src = gdal.Open(filename)
if src is None:
logging.error('unable to open source dataset: "%s"' % filename)
continue
try:
export_raster(src, dst, boxlayer, False, mark_corners=False)
except ValueError as e:
if 'no geographic info' in str(e):
logging.error(str(e))
else:
raise
subdatasets = [subds for subds, descr in src.GetSubDatasets()]
if subdatasets:
compact_index(subdatasets, dst)
[docs]def raster_index(srclist, dst, gcplayer=False, mark_corners=False):
if isinstance(dst, basestring):
dst = create_datasource(dst)
# Bounding box
for src in srclist:
logging.info('adding "%s"' % src)
if isinstance(src, basestring):
filename = src
src = gdal.Open(filename)
if src is None:
logging.error('unable to open source dataset: "%s"' % filename)
continue
try:
export_raster(src, dst, None, gcplayer, mark_corners=mark_corners)
except ValueError as e:
if 'no geographic info' in e.message:
logging.error(str(e))
else:
raise
subdatasets = [subds for subds, descr in src.GetSubDatasets()]
if subdatasets:
raster_index(subdatasets, dst, gcplayer, mark_corners)
[docs]def raster_tree_index(src, dst, boxlayer=None, gcplayer=None,
mark_corners=False):
assert os.path.isdir(src)
if isinstance(dst, basestring):
dst = create_datasource(dst)
gdal.PushErrorHandler('CPLQuietErrorHandler')
for root, dirs, files, in os.walk(src):
for filename in files:
try:
filename = os.path.join(root, filename)
export_raster(filename, dst, boxlayer, gcplayer,
mark_corners=mark_corners)
except (RuntimeError, ValueError):
#logging.exception(str(e))
logging.info('skip "%s"' % filename)
else:
logging.info('adding "%s"' % filename)
gdal.PopErrorHandler()
### Command line tool #########################################################
[docs]def handlecmd(argv=None):
import optparse
# @NOTE: ogr.GeneralCmdLineProcessor is not available in GDAL 1.6.x
argv = gdal.GeneralCmdLineProcessor(argv)
parser = optparse.OptionParser(
usage='%prog [options] OUTPUT INPUT [INPUT [...]]',
version='%%prog %s' % __version__,
description=__doc__)
#parser.add_option('-o', '--outfile', type='str',
# help='output file name (default: generated)')
#parser.add_option('-f', '--format', type='str', default='KML',
# help='output vector format (default: %default)')
#parser.add_option('-s', '--t_srs', type='str', default='EPSG:4326'
# help='target spatial reference system '
# '(default: %default)')
parser.add_option('-g', '--gcps', action='store_true', default=False,
help='generate an additional layer for GCPs '
'(default: %default)')
parser.add_option('-c', '--corners', action='store_true', default=False,
help='generate markers for bounding box corners '
'(default: %default)')
parser.add_option('-a', '--abspath', action='store_true', default=False,
help='store absolute path in bounding box feature '
'description (default: %default)')
options, args = parser.parse_args()
if len(args) < 2:
parser.error('at least two arguments are required.')
#~ if options.t_srs and options.format in ('KML', 'LIBKML'):
#~ epsg4326 = osr.SpatialReference()
#~ epsg4326.SetWellKnownGeogCS('EPSG:4326')
#~ tsrs = osr.SpatialReference()
#~ tsrs.SetFromUserInput(oprions.t_srs)
#~ if not epsg4326.IsSame(tsrs):
#~ logging.warning('KML format only supports "EPSG:4326" as '
#~ 'target spatial reference system: '
#~ '"t_srs" parameter will be ignred.')
#~ options.t_srs = epsg4326
#~ else:
#~ options.t_srs = tsrs
return options, args
[docs]def main(*argv):
logging.basicConfig(format='%(levelname)s: %(message)s',
level=logging.INFO)
try:
if not argv:
argv = sys.argv
options, args = handlecmd(argv)
outfile = args.pop(0)
if os.path.exists(outfile):
logging.error('the output file ("%s") already exists.' % outfile)
sys.exit(EX_USAGE)
dst = create_datasource(outfile) # , options.format)
if len(args) > 1:
if options.abspath:
args = [os.path.abspath(name) for name in args]
if options.gcps or options.corners:
raster_index(args, dst, options.gcps, options.corners)
else:
compact_index(args, dst)
else:
inputpath = args[0]
if options.abspath:
inputpath = os.path.abspath(inputpath)
if options.gcps:
gcplayer = 'GCPs'
else:
gcplayer = False
if os.path.isdir(inputpath):
if options.gcps or options.corners:
boxlayer = None
else:
boxlayer = os.path.basename(os.path.normpath(inputpath))
raster_tree_index(inputpath, dst,
boxlayer=boxlayer, gcplayer=options.gcps,
mark_corners=options.corners)
else:
export_raster(inputpath, dst,
boxlayer='box', gcplayer=gcplayer,
mark_corners=options.corners)
except Exception as e:
logging.error(str(e), exc_info=True)
sys.exit(1)
if __name__ == '__main__':
main()