#!/usr/bin/env python
#/******************************************************************************
# * $Id: ogrinfo.py 23329 2011-11-05 21:09:07Z rouault $
# *
# * Project:  OpenGIS Simple Features Reference Implementation
# * Purpose:  Python port of a simple client for viewing OGR driver data.
# * Author:   Even Rouault, <even dot rouault at mines dash paris dot org>
# *
# * Port from ogrinfo.cpp whose author is Frank Warmerdam
# *
# ******************************************************************************
# * Copyright (c) 2010, Even Rouault
# * Copyright (c) 1999, Frank Warmerdam
# *
# * Permission is hereby granted, free of charge, to any person obtaining a
# * copy of this software and associated documentation files (the "Software"),
# * to deal in the Software without restriction, including without limitation
# * the rights to use, copy, modify, merge, publish, distribute, sublicense,
# * and/or sell copies of the Software, and to permit persons to whom the
# * Software is furnished to do so, subject to the following conditions:
# *
# * The above copyright notice and this permission notice shall be included
# * in all copies or substantial portions of the Software.
# *
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# * DEALINGS IN THE SOFTWARE.
# ****************************************************************************/

# Note : this is the most direct port of ogrinfo.cpp possible
# It could be made much more Python'ish !

import sys

try:
    from osgeo import gdal
    from osgeo import ogr
except:
    import gdal
    import ogr

bReadOnly = False
bVerbose = True
bSummaryOnly = False
nFetchFID = ogr.NullFID
papszOptions = None

def EQUAL(a, b):
    return a.lower() == b.lower()

#/************************************************************************/
#/*                                main()                                */
#/************************************************************************/

def main(argv = None):
    
    global bReadOnly
    global bVerbose
    global bSummaryOnly
    global nFetchFID
    global papszOptions
    
    pszWHERE = None
    pszDataSource = None
    papszLayers = None
    poSpatialFilter = None
    nRepeatCount = 1
    bAllLayers = False
    pszSQLStatement = None
    pszDialect = None
    options = {}

    if argv is None:
        argv = sys.argv

    argv = ogr.GeneralCmdLineProcessor( argv )

#/* -------------------------------------------------------------------- */
#/*      Processing command line arguments.                              */
#/* -------------------------------------------------------------------- */
    if argv is None:
        return 1

    nArgc = len(argv)

    iArg = 1
    while iArg < nArgc:

        if EQUAL(argv[iArg],"--utility_version"):
            print("%s is running against GDAL %s" %
                   (argv[0], gdal.VersionInfo("RELEASE_NAME")))
            return 0

        elif EQUAL(argv[iArg],"-ro"):
            bReadOnly = True
        elif EQUAL(argv[iArg],"-q") or EQUAL(argv[iArg],"-quiet"):
            bVerbose = False
        elif EQUAL(argv[iArg],"-fid") and iArg < nArgc-1:
            iArg = iArg + 1
            nFetchFID = int(argv[iArg])
        elif EQUAL(argv[iArg],"-spat") and iArg + 4 < nArgc:
            oRing = ogr.Geometry(ogr.wkbLinearRing)

            oRing.AddPoint( float(argv[iArg+1]), float(argv[iArg+2]) )
            oRing.AddPoint( float(argv[iArg+1]), float(argv[iArg+4]) )
            oRing.AddPoint( float(argv[iArg+3]), float(argv[iArg+4]) )
            oRing.AddPoint( float(argv[iArg+3]), float(argv[iArg+2]) )
            oRing.AddPoint( float(argv[iArg+1]), float(argv[iArg+2]) )

            poSpatialFilter = ogr.Geometry(ogr.wkbPolygon)
            poSpatialFilter.AddGeometry(oRing)
            iArg = iArg + 4

        elif EQUAL(argv[iArg],"-where") and iArg < nArgc-1:
            iArg = iArg + 1
            pszWHERE = argv[iArg]

        elif EQUAL(argv[iArg],"-sql") and iArg < nArgc-1:
            iArg = iArg + 1
            pszSQLStatement = argv[iArg]

        elif EQUAL(argv[iArg],"-dialect") and iArg < nArgc-1:
            iArg = iArg + 1
            pszDialect = argv[iArg]

        elif EQUAL(argv[iArg],"-rc") and iArg < nArgc-1:
            iArg = iArg + 1
            nRepeatCount = int(argv[iArg])

        elif EQUAL(argv[iArg],"-al"):
            bAllLayers = True

        elif EQUAL(argv[iArg],"-so") or EQUAL(argv[iArg],"-summary"):
            bSummaryOnly = True

        elif len(argv[iArg]) > 8 and EQUAL(argv[iArg][0:8],"-fields="):
            options['DISPLAY_FIELDS'] = argv[iArg][7:len(argv[iArg])]

        elif len(argv[iArg]) > 6 and EQUAL(argv[iArg][0:6],"-geom="):
            options['DISPLAY_GEOMETRY'] = argv[iArg][6:len(argv[iArg])]

        elif argv[iArg][0] == '-':
            return Usage()

        elif pszDataSource is None:
            pszDataSource = argv[iArg]
        else:
            if papszLayers is None:
                papszLayers = []
            papszLayers.append( argv[iArg] )
            bAllLayers = False

        iArg = iArg + 1

    if pszDataSource is None:
        return Usage()

#/* -------------------------------------------------------------------- */
#/*      Open data source.                                               */
#/* -------------------------------------------------------------------- */
    poDS = None
    poDriver = None

    poDS = ogr.Open( pszDataSource, not bReadOnly )
    if poDS is None and not bReadOnly:
        poDS = ogr.Open( pszDataSource, False )
        if poDS is not None and bVerbose:
            print( "Had to open data source read-only." )
            bReadOnly = True

#/* -------------------------------------------------------------------- */
#/*      Report failure                                                  */
#/* -------------------------------------------------------------------- */
    if poDS is None:
        print( "FAILURE:\n"
                "Unable to open datasource `%s' with the following drivers." % pszDataSource )

        for iDriver in range(ogr.GetDriverCount()):
            print( "  -> %s" % ogr.GetDriver(iDriver).GetName() )

        return 1

    poDriver = poDS.GetDriver()

#/* -------------------------------------------------------------------- */
#/*      Some information messages.                                      */
#/* -------------------------------------------------------------------- */
    if bVerbose:
        print( "INFO: Open of `%s'\n"
                "      using driver `%s' successful." % (pszDataSource, poDriver.GetName()) )

    poDS_Name = poDS.GetName()
    if str(type(pszDataSource)) == "<type 'unicode'>" and str(type(poDS_Name)) == "<type 'str'>":
        poDS_Name = unicode(poDS_Name, "utf8")
    if bVerbose and pszDataSource != poDS_Name:
        print( "INFO: Internal data source name `%s'\n"
                "      different from user name `%s'." % (poDS_Name, pszDataSource ))

#/* -------------------------------------------------------------------- */
#/*      Special case for -sql clause.  No source layers required.       */
#/* -------------------------------------------------------------------- */
    if pszSQLStatement is not None:
        poResultSet = None

        nRepeatCount = 0  #// skip layer reporting.

        if papszLayers is not None:
            print( "layer names ignored in combination with -sql." )

        poResultSet = poDS.ExecuteSQL( pszSQLStatement, poSpatialFilter, 
                                        pszDialect )

        if poResultSet is not None:
            if pszWHERE is not None:
                if poResultSet.SetAttributeFilter( pszWHERE ) != 0:
                    print("FAILURE: SetAttributeFilter(%s) failed." % pszWHERE)
                    return 1

            ReportOnLayer( poResultSet, None, None, options )
            poDS.ReleaseResultSet( poResultSet )

    #gdal.Debug( "OGR", "GetLayerCount() = %d\n", poDS.GetLayerCount() )

    for iRepeat in range(nRepeatCount):
        if papszLayers is None:
#/* -------------------------------------------------------------------- */ 
#/*      Process each data source layer.                                 */ 
#/* -------------------------------------------------------------------- */ 
            for iLayer in range(poDS.GetLayerCount()):
                poLayer = poDS.GetLayer(iLayer)

                if poLayer is None:
                    print( "FAILURE: Couldn't fetch advertised layer %d!" % iLayer )
                    return 1

                if not bAllLayers:
                    line = "%d: %s" % (iLayer+1, poLayer.GetLayerDefn().GetName())

                    if poLayer.GetLayerDefn().GetGeomType() != ogr.wkbUnknown:
                        line = line + " (%s)" % ogr.GeometryTypeToName( poLayer.GetLayerDefn().GetGeomType() )

                    print(line)
                else:
                    if iRepeat != 0:
                        poLayer.ResetReading()

                    ReportOnLayer( poLayer, pszWHERE, poSpatialFilter, options )

        else:
#/* -------------------------------------------------------------------- */ 
#/*      Process specified data source layers.                           */ 
#/* -------------------------------------------------------------------- */ 
            for papszIter in papszLayers:
                poLayer = poDS.GetLayerByName(papszIter)

                if poLayer is None:
                    print( "FAILURE: Couldn't fetch requested layer %s!" % papszIter )
                    return 1

                if iRepeat != 0:
                    poLayer.ResetReading()

                ReportOnLayer( poLayer, pszWHERE, poSpatialFilter, options )

#/* -------------------------------------------------------------------- */
#/*      Close down.                                                     */
#/* -------------------------------------------------------------------- */
    poDS.Destroy()

    return 0

#/************************************************************************/
#/*                               Usage()                                */
#/************************************************************************/

def Usage():

    print( "Usage: ogrinfo [--help-general] [-ro] [-q] [-where restricted_where]\n"
            "               [-spat xmin ymin xmax ymax] [-fid fid]\n"
            "               [-sql statement] [-al] [-so] [-fields={YES/NO}]\n"
            "               [-geom={YES/NO/SUMMARY}][--formats]\n"
            "               datasource_name [layer [layer ...]]")
    return 1

#/************************************************************************/
#/*                           ReportOnLayer()                            */
#/************************************************************************/

def ReportOnLayer( poLayer, pszWHERE, poSpatialFilter, options ):

    poDefn = poLayer.GetLayerDefn()

#/* -------------------------------------------------------------------- */
#/*      Set filters if provided.                                        */
#/* -------------------------------------------------------------------- */
    if pszWHERE is not None:
        if poLayer.SetAttributeFilter( pszWHERE ) != 0:
            print("FAILURE: SetAttributeFilter(%s) failed." % pszWHERE)
            return

    if poSpatialFilter is not None:
        poLayer.SetSpatialFilter( poSpatialFilter )

#/* -------------------------------------------------------------------- */
#/*      Report various overall information.                             */
#/* -------------------------------------------------------------------- */
    print( "" )
    
    print( "Layer name: %s" % poDefn.GetName() )

    if bVerbose:
        print( "Geometry: %s" % ogr.GeometryTypeToName( poDefn.GetGeomType() ) )
        
        print( "Feature Count: %d" % poLayer.GetFeatureCount() )
        
        oExt = poLayer.GetExtent(True, can_return_null = True)
        if oExt is not None:
            print("Extent: (%f, %f) - (%f, %f)" % (oExt[0], oExt[1], oExt[2], oExt[3]))

        if poLayer.GetSpatialRef() is None:
            pszWKT = "(unknown)"
        else:
            pszWKT = poLayer.GetSpatialRef().ExportToPrettyWkt()

        print( "Layer SRS WKT:\n%s" % pszWKT )
    
        if len(poLayer.GetFIDColumn()) > 0:
            print( "FID Column = %s" % poLayer.GetFIDColumn() )
    
        if len(poLayer.GetGeometryColumn()) > 0:
            print( "Geometry Column = %s" % poLayer.GetGeometryColumn() )

        for iAttr in range(poDefn.GetFieldCount()):
            poField = poDefn.GetFieldDefn( iAttr )
            
            print( "%s: %s (%d.%d)" % ( \
                    poField.GetNameRef(), \
                    poField.GetFieldTypeName( poField.GetType() ), \
                    poField.GetWidth(), \
                    poField.GetPrecision() ))

#/* -------------------------------------------------------------------- */
#/*      Read, and dump features.                                        */
#/* -------------------------------------------------------------------- */
    poFeature = None

    if nFetchFID == ogr.NullFID and not bSummaryOnly:

        poFeature = poLayer.GetNextFeature()
        while poFeature is not None:
            DumpReadableFeature(poFeature, options)
            poFeature = poLayer.GetNextFeature()

    elif nFetchFID != ogr.NullFID:

        poFeature = poLayer.GetFeature( nFetchFID )
        if poFeature is None:
            print( "Unable to locate feature id %d on this layer." % nFetchFID )

        else:
            DumpReadableFeature(poFeature, options)

    return


def DumpReadableFeature( poFeature, options = None ):

    poDefn = poFeature.GetDefnRef()
    print("OGRFeature(%s):%ld" % (poDefn.GetName(), poFeature.GetFID() ))

    if 'DISPLAY_FIELDS' not in options or EQUAL(options['DISPLAY_FIELDS'], 'yes'):
        for iField in range(poDefn.GetFieldCount()):

            poFDefn = poDefn.GetFieldDefn(iField)

            line =  "  %s (%s) = " % ( \
                    poFDefn.GetNameRef(), \
                    ogr.GetFieldTypeName(poFDefn.GetType()) )

            if poFeature.IsFieldSet( iField ):
                line = line + "%s" % (poFeature.GetFieldAsString( iField ) )
            else:
                line = line + "(null)"

            print(line)


    if poFeature.GetStyleString() is not None:

        if 'DISPLAY_STYLE' not in options or EQUAL(options['DISPLAY_STYLE'], 'yes'):
            print("  Style = %s" % GetStyleString() )

    poGeometry = poFeature.GetGeometryRef()
    if poGeometry is not None:
        if 'DISPLAY_GEOMETRY' not in options or not EQUAL(options['DISPLAY_GEOMETRY'], 'no'):
            DumpReadableGeometry( poGeometry, "  ", options)

    print('')

    return


def DumpReadableGeometry( poGeometry, pszPrefix, options ):

    if pszPrefix == None:
        pszPrefix = ""

    if 'DISPLAY_GEOMETRY' in options and EQUAL(options['DISPLAY_GEOMETRY'], 'SUMMARY'):

        line = ("%s%s : " % (pszPrefix, poGeometry.GetGeometryName() ))
        eType = poGeometry.GetGeometryType()
        if eType == ogr.wkbLineString or eType == ogr.wkbLineString25D:
            line = line + ("%d points" % poGeometry.GetPointCount())
            print(line)
        elif eType == ogr.wkbPolygon or eType == ogr.wkbPolygon25D:
            nRings = poGeometry.GetGeometryCount()
            if nRings == 0:
                line = line + "empty"
            else:
                poRing = poGeometry.GetGeometryRef(0)
                line = line + ("%d points" % poRing.GetPointCount())
                if nRings > 1:
                    line = line + (", %d inner rings (" % (nRings - 1))
                    for ir in range(0,nRings-1):
                        if ir > 0:
                            line = line + ", "
                        poRing = poGeometry.GetGeometryRef(ir+1)
                        line = line + ("%d points" % poRing.GetPointCount())
                    line = line + ")"
            print(line)

        elif eType == ogr.wkbMultiPoint or \
            eType == ogr.wkbMultiPoint25D or \
            eType == ogr.wkbMultiLineString or \
            eType == ogr.wkbMultiLineString25D or \
            eType == ogr.wkbMultiPolygon or \
            eType == ogr.wkbMultiPolygon25D or \
            eType == ogr.wkbGeometryCollection or \
            eType == ogr.wkbGeometryCollection25D:

                line = line + "%d geometries:" % poGeometry.GetGeometryCount()
                print(line)
                for ig in range(poGeometry.GetGeometryCount()):
                    subgeom = poGeometry.GetGeometryRef(ig)
                    from sys import version_info
                    if version_info >= (3,0,0):
                        exec('print("", end=" ")')
                    else:
                        exec('print "", ')
                    DumpReadableGeometry( subgeom, pszPrefix, options)
        else:
            print(line)

    elif 'DISPLAY_GEOMETRY' not in options or EQUAL(options['DISPLAY_GEOMETRY'], 'yes') \
            or EQUAL(options['DISPLAY_GEOMETRY'], 'WKT'):

        print("%s%s" % (pszPrefix, poGeometry.ExportToWkt() ))

    return

if __name__ == '__main__':
    version_num = int(gdal.VersionInfo('VERSION_NUM'))
    if version_num < 1800: # because of ogr.GetFieldTypeName
        print('ERROR: Python bindings of GDAL 1.8.0 or later required')
        sys.exit(1)

    sys.exit(main( sys.argv ))
