- Main Page
- Classes
- Files
- Directories
- Related Pages

# RFC 6: OGR support for querying and selection by geometry

and feature style

Author: Tamas Szekeres
Contact: szekerest@gmail.com
Status: Proposed

## Summary

This proposal addresses and issue have been discovered long ago, and OGR provides no equivalent solution so far.

Some of the supported formats like Mapinfo.tab may contain multiple geometry types and style information. In order to hanlde this kind of data sources properly a support for selecting the layers by geometry type or by the style info would be highly required. For more details see the following MapServer related bugs later in this document.

All of the proposed changes can be found at the tracking bug of this RFC referenced later in this document.

## Main concepts

The most reasonable way to support this feature is to extend the currently existing 'special field' approach to allow specifying more than one fields. Along with the already definied 'FID' field we will add the following ones:

- 'OGR_GEOMETRY' containing the geometry type like 'POINT' or 'POLYGON'.

- 'OGR_STYLE' containing the style string.

- 'OGR_GEOM_WKT' containing the full WKT of the geometry.

By providing the aforementioned fields one can make for example the following selections:

- select FID, OGR_GEOMETRY, OGR_STYLE, OGR_GEOM_WKT, * from MyTable where OGR_GEOMETRY='POINT' OR OGR_GEOMETRY='POLYGON'

- select FID, OGR_GEOMETRY, OGR_STYLE, OGR_GEOM_WKT, * from MyTable where OGR_STYLE LIKE 'BRUSH'

- select FID, OGR_GEOMETRY, OGR_STYLE, OGR_GEOM_WKT, * from MyTable where OGR_GEOM_WKT LIKE 'POLYGON'

- select distinct OGR_GEOMETRY from MyTable order by OGR_GEOMETRY desc

## Implementation

There are two distinct areas where this feature plays a role

- Feature query implemented at ogrfeaturequery.cpp

- SQL based selection implemented at ogr_gensql.cpp and ogrdatasource.cpp

To specify arbitrary number of special fields we will declare an array for the field names and types in ogrfeaturequery.cpp as

    char* SpecialFieldNames[SPECIAL_FIELD_COUNT]
        = {"FID", "OGR_GEOMETRY", "OGR_STYLE", "OGR_GEOM_WKT"};
    swq_field_type SpecialFieldTypes[SPECIAL_FIELD_COUNT]
        = {SWQ_INTEGER, SWQ_STRING, SWQ_STRING, SWQ_STRING};

So as to make this array accessible to the other files the followings will be added to ogr_p.h

    CPL_C_START
    include "swq.h"
    CPL_C_END

    define SPECIAL_FIELD_COUNT 4
    extern char* SpecialFieldNames[SPECIAL_FIELD_COUNT];
    extern swq_field_type SpecialFieldTypes[SPECIAL_FIELD_COUNT];

In ogrfeaturequery.cpp we should change OGRFeatureQuery::Compile to add the special fields like:

    iField = 0;
    while (iField < SPECIAL_FIELD_COUNT)
    {
        papszFieldNames[poDefn->GetFieldCount() + iField] = SpecialFieldNames[iField];
        paeFieldTypes[poDefn->GetFieldCount() + iField] = SpecialFieldTypes[iField];
        ++iField;
    }

In ogrfeaturequery.cpp OGRFeatureQueryEvaluator() should be modifyed according to the field specific actions like

    switch (op->field_index - poFeature->GetDefnRef()->GetFieldCount())
    {
    case 0:
        sField.Integer = poFeature->GetFID();
        break;
    case 1:
        sField.String = (char*) poFeature->GetGeometryRef()->getGeometryName();
        /* should not destroy string literals */
        break;
    case 2:
        sField.String = (char*) poFeature->GetStyleString();
        /* should not destroy the style string managed by OGRFeature */
        break;
    case 3:
        if ( poFeature->GetGeometryRef()->exportToWkt( &sField.String ) != OGRERR_NONE )
            return FALSE;
        /* should finalize */
        finalizer.Add( sField.String );
    	break;
    default:
        CPLDebug( "OGRFeatureQuery", "Illegal special field index.");
        return FALSE;
    }

So as to deallocate the newly created wkt in the previous code we have introduced a helper class 'finalizer' as

    class StringFinalizer
    {
    public:
        StringFinalizer( ) { _ptr = NULL; }
        ~StringFinalizer()
        {
        if ( _ptr )
            CPLFree( _ptr );
        }
        void Add( char* ptr)
        {
            _ptr = ptr;
        }
    private:
        char* _ptr;
    };

In ogrfeaturequery.cpp OGRFeatureQuery::FieldCollector should be modifyed to add the field names like:

    if( op->field_index >= poTargetDefn->GetFieldCount()
            && op->field_index < poTargetDefn->GetFieldCount() + SPECIAL_FIELD_COUNT)
            pszFieldName = SpecialFieldNames[op->field_index];

In ogrdatasource.cpp ExecuteSQL() will allocate the arrays according to the number of the special fields:

    sFieldList.names = (char **)
            CPLMalloc( sizeof(char *) * (nFieldCount+SPECIAL_FIELD_COUNT) );
    sFieldList.types = (swq_field_type *)
            CPLMalloc( sizeof(swq_field_type) * (nFieldCount+SPECIAL_FIELD_COUNT) );
    sFieldList.table_ids = (int *)
            CPLMalloc( sizeof(int) * (nFieldCount+SPECIAL_FIELD_COUNT) );
    sFieldList.ids = (int *)
            CPLMalloc( sizeof(int) * (nFieldCount+SPECIAL_FIELD_COUNT) );

And the fields will be added as

    for (iField = 0; iField < SPECIAL_FIELD_COUNT; iField++)
    {
        sFieldList.names[sFieldList.count] = SpecialFieldNames[iField];
        sFieldList.types[sFieldList.count] = SpecialFieldTypes[iField];
        sFieldList.table_ids[sFieldList.count] = 0;
        sFieldList.ids[sFieldList.count] = nFIDIndex + iField;
        sFieldList.count++;
    }

For supporting the SQL based queries we should also modify the constructor of OGRGenSQLResultsLayer in ogr_gensql.cpp and set the field type properly:

    else if ( psColDef->field_index >= iFIDFieldIndex )
    {
        switch ( SpecialFieldTypes[psColDef->field_index - iFIDFieldIndex] )
        {
        case SWQ_INTEGER:
            oFDefn.SetType( OFTInteger );
            break;
        case SWQ_STRING:
            oFDefn.SetType( OFTString );
            break;
        }
    }

Some of the queries will require to modify OGRGenSQLResultsLayer::PrepareSummary in ogr_gensql.cpp:

    if( psColDef->field_index == iFIDFieldIndex )
    {
        // Special case where the column is "FID"
        char szBuffer[255];
        sprintf( szBuffer, "%ld", poSrcFeature->GetFID() );
        pszError =
            swq_select_summarize( psSelectInfo, iField, szBuffer);
    }
    else if( psColDef->field_index == iFIDFieldIndex + 1 )
    {
        // Special case where the column is "OGR_GEOMETRY"
        pszError =
            swq_select_summarize( psSelectInfo, iField, poSrcFeature->GetGeometryRef()->getGeometryName());
    }
    else if( psColDef->field_index == iFIDFieldIndex + 2 )
    {
        // Special case where the column is "OGR_STYLE"
        pszError =
            swq_select_summarize( psSelectInfo, iField, poSrcFeature->GetStyleString());
    }
    else if( psColDef->field_index == iFIDFieldIndex + 3 )
    {
        // Special case where the column is "OGR_GEOMETRY_WKT"
        char *wkt = NULL;
        if ( poSrcFeature->GetGeometryRef()->exportToWkt( &wkt ) != OGRERR_NONE )
        {
            pszError =
                swq_select_summarize( psSelectInfo, iField, wkt);
        }
        if (wkt)
            CPLFree(wkt);
    }

OGRGenSQLResultsLayer::TranslateFeature should also be modifyed when copying the fields from primary record to the destination feature

    if( psColDef->field_index == iFIDFieldIndex )
        poDstFeat->SetField( iField, (int) poSrcFeat->GetFID() );
    else if( psColDef->field_index == iFIDFieldIndex + 1)
        poDstFeat->SetField( iField, poSrcFeat->GetGeometryRef()->getGeometryName() );
    else if( psColDef->field_index == iFIDFieldIndex + 2)
        poDstFeat->SetField( iField, poSrcFeat->GetStyleString() );
    else if( psColDef->field_index == iFIDFieldIndex + 3)
    {
        char *wkt = NULL;
        if ( poSrcFeat->GetGeometryRef()->exportToWkt( &wkt ) == OGRERR_NONE )
            poDstFeat->SetField( iField, wkt );
        if (wkt)
            CPLFree(wkt);
    }

For supporting the 'order by' queries we should also modify OGRGenSQLResultsLayer::CreateOrderByIndex() as:

    if( psKeyDef->field_index == iFIDFieldIndex )
    {
        psDstField->Integer = poSrcFeat->GetFID();
        continue;
    }
    if( psKeyDef->field_index == iFIDFieldIndex + 1 )
    {
        psDstField->String = CPLStrdup( poSrcFeat->GetGeometryRef()->getGeometryName() );
        continue;
    }
    if( psKeyDef->field_index == iFIDFieldIndex + 2 )
    {
        psDstField->String = CPLStrdup( poSrcFeat->GetStyleString() );
        continue;
    }
    if( psKeyDef->field_index == iFIDFieldIndex + 3 )
    {
        if ( poSrcFeat->GetGeometryRef()->exportToWkt( &psDstField->String ) == OGRERR_NONE )
        continue;
    }

All of the strings ellocated previously should be deallocated later in the same function as:

    if ( psKeyDef->field_index >= iFIDFieldIndex )
    {
        /* warning: only special fields of type string should be deallocated */
        if (SpecialFieldTypes[psKeyDef->field_index - iFIDFieldIndex] == SWQ_STRING)
        {
            for( i = 0; i < nIndexSize; i++ )
            {
                OGRField *psField = pasIndexFields + iKey + i * nOrderItems;
                CPLFree( psField->String );
            }
        }
        continue;
    }

When ordering by the field values the OGRGenSQLResultsLayer::Compare should also be modifyed:

    if( psKeyDef->field_index >= iFIDFieldIndex )
        poFDefn = NULL;
    else
        poFDefn = poSrcLayer->GetLayerDefn()->GetFieldDefn(
            psKeyDef->field_index );

    if( (pasFirstTuple[iKey].Set.nMarker1 == OGRUnsetMarker
            && pasFirstTuple[iKey].Set.nMarker2 == OGRUnsetMarker)
        || (pasSecondTuple[iKey].Set.nMarker1 == OGRUnsetMarker
            && pasSecondTuple[iKey].Set.nMarker2 == OGRUnsetMarker) )
        nResult = 0;
    else if ( poFDefn == NULL )
    {
        switch (SpecialFieldTypes[psKeyDef->field_index - iFIDFieldIndex])
        {
        case SWQ_INTEGER:
            if( pasFirstTuple[iKey].Integer < pasSecondTuple[iKey].Integer )
                nResult = -1;
            else if( pasFirstTuple[iKey].Integer > pasSecondTuple[iKey].Integer )
                nResult = 1;
            break;
        case SWQ_STRING:
            nResult = strcmp(pasFirstTuple[iKey].String,
                            pasSecondTuple[iKey].String);
            break;
        }
    }

## Backward Compatibility

The backward compatibility of the OGR library will be retained

## References

- Tracking bug for this feature (containing all of the proposed code changes):

  http://bugzilla.remotesensing.org/show_bug.cgi?id=1333

- MapServer related bugs:

  http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=1129

  http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=1438

Generated for GDAL by 1.4.5.