| 1 | = RFC 6: Geometry and Feature Style as OGR Special Fields = |
| 2 | |
| 3 | Author: Tamas Szekeres[[BR]] |
| 4 | Contact: szekerest@gmail.com[[BR]] |
| 5 | Status: Adopted |
| 6 | |
| 7 | == Summary == |
| 8 | |
| 9 | This proposal addresses and issue have been discovered long ago, and OGR provides |
| 10 | no equivalent solution so far. |
| 11 | |
| 12 | Some of the supported formats like Mapinfo.tab may contain multiple geometry types |
| 13 | and style information. In order to hanlde this kind of data sources properly a |
| 14 | support for selecting the layers by geometry type or by the style info would be |
| 15 | highly required. For more details see the following MapServer related bugs later |
| 16 | in this document. |
| 17 | |
| 18 | All of the proposed changes can be found at the tracking bug of this RFC |
| 19 | referenced later in this document. |
| 20 | |
| 21 | == Main concepts == |
| 22 | |
| 23 | The most reasonable way to support this feature is to extend the currently |
| 24 | existing 'special field' approach to allow specifying more than one fields. |
| 25 | Along with the already definied 'FID' field we will add the following ones: |
| 26 | |
| 27 | * 'OGR_GEOMETRY' containing the geometry type like 'POINT' or 'POLYGON'. |
| 28 | * 'OGR_STYLE' containing the style string. |
| 29 | * 'OGR_GEOM_WKT' containing the full WKT of the geometry. |
| 30 | |
| 31 | By providing the aforementioned fields one can make for example |
| 32 | the following selections: |
| 33 | |
| 34 | * select FID, OGR_GEOMETRY, OGR_STYLE, OGR_GEOM_WKT, * from MyTable where OGR_GEOMETRY='POINT' OR OGR_GEOMETRY='POLYGON' |
| 35 | * select FID, OGR_GEOMETRY, OGR_STYLE, OGR_GEOM_WKT, * from MyTable where OGR_STYLE LIKE '%BRUSH%' |
| 36 | * select FID, OGR_GEOMETRY, OGR_STYLE, OGR_GEOM_WKT, * from MyTable where OGR_GEOM_WKT LIKE 'POLYGON%' |
| 37 | * select distinct OGR_GEOMETRY from MyTable order by OGR_GEOMETRY desc |
| 38 | |
| 39 | == Implementation == |
| 40 | |
| 41 | There are two distinct areas where this feature plays a role |
| 42 | |
| 43 | * Feature query implemented at ogrfeaturequery.cpp |
| 44 | |
| 45 | * SQL based selection implemented at ogr_gensql.cpp and ogrdatasource.cpp |
| 46 | |
| 47 | To specify arbitrary number of special fields we will declare an |
| 48 | array for the field names and types in ogrfeaturequery.cpp as |
| 49 | |
| 50 | {{{ |
| 51 | char* SpecialFieldNames[SPECIAL_FIELD_COUNT] |
| 52 | = {"FID", "OGR_GEOMETRY", "OGR_STYLE", "OGR_GEOM_WKT"}; |
| 53 | swq_field_type SpecialFieldTypes[SPECIAL_FIELD_COUNT] |
| 54 | = {SWQ_INTEGER, SWQ_STRING, SWQ_STRING, SWQ_STRING}; |
| 55 | }}} |
| 56 | |
| 57 | So as to make this array accessible to the other files the followings |
| 58 | will be added to ogr_p.h |
| 59 | |
| 60 | {{{ |
| 61 | CPL_C_START |
| 62 | #include "swq.h" |
| 63 | CPL_C_END |
| 64 | |
| 65 | #define SPF_FID 0 |
| 66 | #define SPF_OGR_GEOMETRY 1 |
| 67 | #define SPF_OGR_STYLE 2 |
| 68 | #define SPF_OGR_GEOM_WKT 3 |
| 69 | #define SPECIAL_FIELD_COUNT 4 |
| 70 | |
| 71 | extern char* SpecialFieldNames[SPECIAL_FIELD_COUNT]; |
| 72 | extern swq_field_type SpecialFieldTypes[SPECIAL_FIELD_COUNT]; |
| 73 | }}} |
| 74 | |
| 75 | In ogrfeature.cpp the field accessor functions (GetFieldAsString, |
| 76 | GetFieldAsInteger, GetFieldAsDouble) will be modified providing |
| 77 | the values of the special fields by the field index |
| 78 | |
| 79 | The following code will be added to the beginning of OGRFeature::GetFieldAsInteger: |
| 80 | |
| 81 | {{{ |
| 82 | int iSpecialField = iField - poDefn->GetFieldCount(); |
| 83 | if (iSpecialField >= 0) |
| 84 | { |
| 85 | // special field value accessors |
| 86 | switch (iSpecialField) |
| 87 | { |
| 88 | case SPF_FID: |
| 89 | return GetFID(); |
| 90 | default: |
| 91 | return 0; |
| 92 | } |
| 93 | } |
| 94 | }}} |
| 95 | |
| 96 | The following code will be added to the beginning of OGRFeature::GetFieldAsDouble: |
| 97 | |
| 98 | {{{ |
| 99 | int iSpecialField = iField - poDefn->GetFieldCount(); |
| 100 | if (iSpecialField >= 0) |
| 101 | { |
| 102 | // special field value accessors |
| 103 | switch (iSpecialField) |
| 104 | { |
| 105 | case SPF_FID: |
| 106 | return GetFID(); |
| 107 | default: |
| 108 | return 0.0; |
| 109 | } |
| 110 | } |
| 111 | }}} |
| 112 | |
| 113 | The following code will be added to the beginning of OGRFeature::GetFieldAsString: |
| 114 | |
| 115 | {{{ |
| 116 | int iSpecialField = iField - poDefn->GetFieldCount(); |
| 117 | if (iSpecialField >= 0) |
| 118 | { |
| 119 | // special field value accessors |
| 120 | switch (iSpecialField) |
| 121 | { |
| 122 | case SPF_FID: |
| 123 | sprintf( szTempBuffer, "%d", GetFID() ); |
| 124 | return m_pszTmpFieldValue = CPLStrdup( szTempBuffer ); |
| 125 | case SPF_OGR_GEOMETRY: |
| 126 | return poGeometry->getGeometryName(); |
| 127 | case SPF_OGR_STYLE: |
| 128 | return GetStyleString(); |
| 129 | case SPF_OGR_GEOM_WKT: |
| 130 | { |
| 131 | if (poGeometry->exportToWkt( &m_pszTmpFieldValue ) == OGRERR_NONE ) |
| 132 | return m_pszTmpFieldValue; |
| 133 | else |
| 134 | return ""; |
| 135 | } |
| 136 | default: |
| 137 | return ""; |
| 138 | } |
| 139 | } |
| 140 | }}} |
| 141 | |
| 142 | The current implementation of OGRFeature::GetFieldAsString uses a static string to |
| 143 | hold the const char* return value that is highly avoidable and makes the code |
| 144 | thread unsafe. In this regard the 'static char szTempBuffer[80]' will be changed to |
| 145 | non static and a new member will be added to OGRFeature in ogrfeature.h as: |
| 146 | |
| 147 | {{{ |
| 148 | char * m_pszTmpFieldValue; |
| 149 | }}} |
| 150 | |
| 151 | This member will be initialized to NULL at the constructor, and will be freed using |
| 152 | CPLFree() at the destructor of OGRFeature. |
| 153 | |
| 154 | In OGRFeature::GetFieldAsString all of the occurrences of 'return szTempBuffer;' |
| 155 | will be changed to 'return m_pszTmpFieldValue = CPLStrdup( szTempBuffer );' |
| 156 | |
| 157 | OGRFeature::GetFieldAsString is responsible to destroy the old value of |
| 158 | m_pszTmpFieldValue at the beginning of the function: |
| 159 | |
| 160 | {{{ |
| 161 | CPLFree(m_pszTmpFieldValue); |
| 162 | m_pszTmpFieldValue = NULL; |
| 163 | }}} |
| 164 | |
| 165 | In ogrfeaturequery.cpp we should change OGRFeatureQuery::Compile to add the |
| 166 | special fields like: |
| 167 | |
| 168 | {{{ |
| 169 | iField = 0; |
| 170 | while (iField < SPECIAL_FIELD_COUNT) |
| 171 | { |
| 172 | papszFieldNames[poDefn->GetFieldCount() + iField] = SpecialFieldNames[iField]; |
| 173 | paeFieldTypes[poDefn->GetFieldCount() + iField] = SpecialFieldTypes[iField]; |
| 174 | ++iField; |
| 175 | } |
| 176 | }}} |
| 177 | |
| 178 | In ogrfeaturequery.cpp OGRFeatureQueryEvaluator() should be modifyed according to |
| 179 | the field specific actions like |
| 180 | |
| 181 | {{{ |
| 182 | int iSpecialField = op->field_index - poFeature->GetDefnRef()->GetFieldCount(); |
| 183 | if( iSpecialField >= 0 ) |
| 184 | { |
| 185 | if ( iSpecialField < SPECIAL_FIELD_COUNT ) |
| 186 | { |
| 187 | switch ( SpecialFieldTypes[iSpecialField] ) |
| 188 | { |
| 189 | case SWQ_INTEGER: |
| 190 | sField.Integer = poFeature->GetFieldAsInteger( op->field_index ); |
| 191 | case SWQ_STRING: |
| 192 | sField.String = (char*) poFeature->GetFieldAsString( op->field_index ); |
| 193 | } |
| 194 | } |
| 195 | else |
| 196 | { |
| 197 | CPLDebug( "OGRFeatureQuery", "Illegal special field index."); |
| 198 | return FALSE; |
| 199 | } |
| 200 | psField = &sField; |
| 201 | } |
| 202 | else |
| 203 | psField = poFeature->GetRawFieldRef( op->field_index ); |
| 204 | }}} |
| 205 | |
| 206 | In ogrfeaturequery.cpp OGRFeatureQuery::FieldCollector should be modifyed to |
| 207 | add the field names like: |
| 208 | |
| 209 | {{{ |
| 210 | if( op->field_index >= poTargetDefn->GetFieldCount() |
| 211 | && op->field_index < poTargetDefn->GetFieldCount() + SPECIAL_FIELD_COUNT) |
| 212 | pszFieldName = SpecialFieldNames[op->field_index]; |
| 213 | }}} |
| 214 | |
| 215 | In ogrdatasource.cpp ExecuteSQL() will allocate the arrays according to the |
| 216 | number of the special fields: |
| 217 | |
| 218 | {{{ |
| 219 | sFieldList.names = (char **) |
| 220 | CPLMalloc( sizeof(char *) * (nFieldCount+SPECIAL_FIELD_COUNT) ); |
| 221 | sFieldList.types = (swq_field_type *) |
| 222 | CPLMalloc( sizeof(swq_field_type) * (nFieldCount+SPECIAL_FIELD_COUNT) ); |
| 223 | sFieldList.table_ids = (int *) |
| 224 | CPLMalloc( sizeof(int) * (nFieldCount+SPECIAL_FIELD_COUNT) ); |
| 225 | sFieldList.ids = (int *) |
| 226 | CPLMalloc( sizeof(int) * (nFieldCount+SPECIAL_FIELD_COUNT) ); |
| 227 | }}} |
| 228 | |
| 229 | And the fields will be added as |
| 230 | |
| 231 | {{{ |
| 232 | for (iField = 0; iField < SPECIAL_FIELD_COUNT; iField++) |
| 233 | { |
| 234 | sFieldList.names[sFieldList.count] = SpecialFieldNames[iField]; |
| 235 | sFieldList.types[sFieldList.count] = SpecialFieldTypes[iField]; |
| 236 | sFieldList.table_ids[sFieldList.count] = 0; |
| 237 | sFieldList.ids[sFieldList.count] = nFIDIndex + iField; |
| 238 | sFieldList.count++; |
| 239 | } |
| 240 | }}} |
| 241 | |
| 242 | For supporting the SQL based queries we should also modify the constructor of |
| 243 | OGRGenSQLResultsLayer in ogr_gensql.cpp and set the field type properly: |
| 244 | |
| 245 | {{{ |
| 246 | else if ( psColDef->field_index >= iFIDFieldIndex ) |
| 247 | { |
| 248 | switch ( SpecialFieldTypes[psColDef->field_index - iFIDFieldIndex] ) |
| 249 | { |
| 250 | case SWQ_INTEGER: |
| 251 | oFDefn.SetType( OFTInteger ); |
| 252 | break; |
| 253 | case SWQ_STRING: |
| 254 | oFDefn.SetType( OFTString ); |
| 255 | break; |
| 256 | case SWQ_FLOAT: |
| 257 | oFDefn.SetType( OFTReal ); |
| 258 | break; |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | }}} |
| 263 | |
| 264 | Some of the queries will require to modify OGRGenSQLResultsLayer::PrepareSummary |
| 265 | in ogr_gensql.cpp will be simplified |
| 266 | (GetFieldAsString will be used in all cases to access the field values): |
| 267 | |
| 268 | {{{ |
| 269 | pszError = swq_select_summarize( psSelectInfo, iField, |
| 270 | poSrcFeature->GetFieldAsString( psColDef->field_index ) ); |
| 271 | }}} |
| 272 | |
| 273 | OGRGenSQLResultsLayer::TranslateFeature should also be modifyed when |
| 274 | copying the fields from primary record to the destination feature |
| 275 | |
| 276 | {{{ |
| 277 | if ( psColDef->field_index >= iFIDFieldIndex && |
| 278 | psColDef->field_index < iFIDFieldIndex + SPECIAL_FIELD_COUNT ) |
| 279 | { |
| 280 | switch (SpecialFieldTypes[psColDef->field_index - iFIDFieldIndex]) |
| 281 | { |
| 282 | case SWQ_INTEGER: |
| 283 | poDstFeat->SetField( iField, poSrcFeat->GetFieldAsInteger(psColDef->field_index) ); |
| 284 | case SWQ_STRING: |
| 285 | poDstFeat->SetField( iField, poSrcFeat->GetFieldAsString(psColDef->field_index) ); |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | }}} |
| 290 | |
| 291 | For supporting the 'order by' queries we should also modify |
| 292 | OGRGenSQLResultsLayer::CreateOrderByIndex() as: |
| 293 | |
| 294 | {{{ |
| 295 | |
| 296 | if ( psKeyDef->field_index >= iFIDFieldIndex) |
| 297 | { |
| 298 | if ( psKeyDef->field_index < iFIDFieldIndex + SPECIAL_FIELD_COUNT ) |
| 299 | { |
| 300 | switch (SpecialFieldTypes[psKeyDef->field_index - iFIDFieldIndex]) |
| 301 | { |
| 302 | case SWQ_INTEGER: |
| 303 | psDstField->Integer = poSrcFeat->GetFieldAsInteger(psKeyDef->field_index); |
| 304 | case SWQ_STRING: |
| 305 | psDstField->String = CPLStrdup( poSrcFeat->GetFieldAsString(psKeyDef->field_index) ); |
| 306 | } |
| 307 | } |
| 308 | continue; |
| 309 | } |
| 310 | |
| 311 | }}} |
| 312 | |
| 313 | All of the strings allocated previously should be deallocated later in |
| 314 | the same function as: |
| 315 | |
| 316 | {{{ |
| 317 | |
| 318 | if ( psKeyDef->field_index >= iFIDFieldIndex ) |
| 319 | { |
| 320 | /* warning: only special fields of type string should be deallocated */ |
| 321 | if (SpecialFieldTypes[psKeyDef->field_index - iFIDFieldIndex] == SWQ_STRING) |
| 322 | { |
| 323 | for( i = 0; i < nIndexSize; i++ ) |
| 324 | { |
| 325 | OGRField *psField = pasIndexFields + iKey + i * nOrderItems; |
| 326 | CPLFree( psField->String ); |
| 327 | } |
| 328 | } |
| 329 | continue; |
| 330 | } |
| 331 | |
| 332 | }}} |
| 333 | |
| 334 | When ordering by the field values the OGRGenSQLResultsLayer::Compare |
| 335 | should also be modifyed: |
| 336 | |
| 337 | {{{ |
| 338 | if( psKeyDef->field_index >= iFIDFieldIndex ) |
| 339 | poFDefn = NULL; |
| 340 | else |
| 341 | poFDefn = poSrcLayer->GetLayerDefn()->GetFieldDefn( |
| 342 | psKeyDef->field_index ); |
| 343 | |
| 344 | if( (pasFirstTuple[iKey].Set.nMarker1 == OGRUnsetMarker |
| 345 | && pasFirstTuple[iKey].Set.nMarker2 == OGRUnsetMarker) |
| 346 | || (pasSecondTuple[iKey].Set.nMarker1 == OGRUnsetMarker |
| 347 | && pasSecondTuple[iKey].Set.nMarker2 == OGRUnsetMarker) ) |
| 348 | nResult = 0; |
| 349 | else if ( poFDefn == NULL ) |
| 350 | { |
| 351 | switch (SpecialFieldTypes[psKeyDef->field_index - iFIDFieldIndex]) |
| 352 | { |
| 353 | case SWQ_INTEGER: |
| 354 | if( pasFirstTuple[iKey].Integer < pasSecondTuple[iKey].Integer ) |
| 355 | nResult = -1; |
| 356 | else if( pasFirstTuple[iKey].Integer > pasSecondTuple[iKey].Integer ) |
| 357 | nResult = 1; |
| 358 | break; |
| 359 | case SWQ_STRING: |
| 360 | nResult = strcmp(pasFirstTuple[iKey].String, |
| 361 | pasSecondTuple[iKey].String); |
| 362 | break; |
| 363 | } |
| 364 | } |
| 365 | }}} |
| 366 | |
| 367 | == Adding New Special Fields == |
| 368 | |
| 369 | Adding a new special field in a subsequent development phase is fairly |
| 370 | straightforward and the following steps should be made: |
| 371 | |
| 372 | 1. In ogr_p.h a new constant should be added with the value of the SPECIAL_FIELD_COUNT and SPECIAL_FIELD_COUNT should be incremented by one. |
| 373 | |
| 374 | 2. In ogrfeaturequery.cpp the special field string and the type should be added to SpecialFieldNames and SpecialFieldTypes respectively |
| 375 | |
| 376 | 3. The field value accessors (OGRFeature::GetFieldAsString, OGRFeature::GetFieldAsInteger, OGRFeature::GetFieldAsDouble) should be modifyed to provide the value of the new special field. All of these functions provide const return values so GetFieldAsString should retain the value in the m_pszTmpFieldValue member. |
| 377 | |
| 378 | 4. When adding a new value with a type other than SWQ_INTEGER and SWQ_STRING the following functions might also be modified accordingly: |
| 379 | * OGRGenSQLResultsLayer::OGRGenSQLResultsLayer |
| 380 | * OGRGenSQLResultsLayer::TranslateFeature |
| 381 | * OGRGenSQLResultsLayer::CreateOrderByIndex |
| 382 | * OGRGenSQLResultsLayer::Compare |
| 383 | * OGRFeatureQueryEvaluator |
| 384 | |
| 385 | == Backward Compatibility == |
| 386 | |
| 387 | In most cases the backward compatibility of the OGR library will be retained. |
| 388 | However the special fields will potentially conflict with regard fields with |
| 389 | the given names. When accessing the field values the special fields will |
| 390 | take pecedence over the other fields with the same names. |
| 391 | |
| 392 | When using OGRFeature::GetFieldAsString the returned value will be stored as |
| 393 | a member variable instead of a static variable. The string will be deallocated |
| 394 | and will no longer be usable after the destruction of the feature. |
| 395 | |
| 396 | == Regression Testing == |
| 397 | |
| 398 | A new gdalautotest/ogr/ogr_sqlspecials.py script to test support for all |
| 399 | special fields in the ExecuteSQL() call and with WHERE clauses. |
| 400 | |
| 401 | == Documentation == |
| 402 | |
| 403 | The OGR SQL document will be updated to reflect the support for special fields. |
| 404 | |
| 405 | == Implementation Staffing == |
| 406 | |
| 407 | Tamas Szekeres will implement the bulk of the RFC in time for GDAL/OGR 1.4.0. |
| 408 | |
| 409 | Frank Warmerdam will consider how the backward compatibility issues |
| 410 | (with special regard to the modified lifespan of the GetFieldAsString returned value) |
| 411 | will affect the other parts of the OGR project and will write the |
| 412 | Python regression testing script. |
| 413 | |
| 414 | == References == |
| 415 | |
| 416 | * Tracking bug for this feature (containing all of the proposed code changes): #1333 |
| 417 | |
| 418 | * MapServer related bugs: |
| 419 | * [http://trac.osgeo.org/mapserver/ticket/1129 1129] |
| 420 | * [http://trac.osgeo.org/mapserver/ticket/1438 1438] |
| 421 | |
| 422 | == Voting History == |
| 423 | |
| 424 | Frank Warmerdam +1[[BR]] |
| 425 | Daniel Morissette +1[[BR]] |
| 426 | Howard Butler +0[[BR]] |
| 427 | Andrey Kiselev +1[[BR]] |