1.. _rfc-61: 2 3======================================================================================= 4RFC 61 : Support for measured geometries 5======================================================================================= 6 7Author: Ari Jolma 8 9Contact: ari.jolma at gmail.com 10 11Status: Adopted 12 13Implementation in version: 2.1 14 15Summary 16------- 17 18This RFC defines how to implement measured geometries (geometries, where 19the points have M coordinate, i.e., they are XYM or XYZM). 20 21Rationale 22--------- 23 24An M coordinate, which is also known as "measure", is an additional 25value that can be stored for each point of a geometry (IBM Technical 26Note, 27`https://www-304.ibm.com/support/docview.wss?uid=swg21054384 <https://www-304.ibm.com/support/docview.wss?uid=swg21054384>`__). 28 29M coordinate is in the OGC simple feature model and it is used in many 30vector data formats. 31 32Changes 33------- 34 35Changes are required into the C++ API and the C API needs to be 36enhanced. Several drivers need to be changed to take advantage of this 37enhancement but also due to the changes in the C++ API. 38 39Common API 40~~~~~~~~~~ 41 42New OGRwkbGeometryType values are needed. SFSQL 1.2 and ISO SQL/MM Part 433 will be used, i.e., 2D type + 2000 for M and 2D type + 3000 for ZM. 44(Also types such as Tin, PolyhedralSurface and Triangle types can be 45added for completeness, even if unimplemented currently). wkbCurve and 46wkbSurface have been moved from #define to the OGRwkbGeometryType 47enumerations, and their Z/M/ZM variants have been added as well (per 48#6401) 49 50On a more general note, there could (should?) be a path to using a clean 51set of values and have legacy support as an exception. 52 53Abstract types are defined and not part of the enum. 54 55:: 56 57 // additions to enum OGRwkbGeometryType 58 wkbCurve = 13, /**< Curve (abstract type). ISO SQL/MM Part 3. GDAL >= 2.1 */ 59 wkbSurface = 14, /**< Surface (abstract type). ISO SQL/MM Part 3. GDAL >= 2.1 */ 60 wkbPolyhedralSurface = 15,/**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 61 wkbTIN = 16, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 62 wkbTriangle = 17, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 63 64 wkbCurveZ = 1013, /**< wkbCurve with Z component. ISO SQL/MM Part 3. GDAL >= 2.1 */ 65 wkbSurfaceZ = 1014, /**< wkbSurface with Z component. ISO SQL/MM Part 3. GDAL >= 2.1 */ 66 wkbPolyhedralSurfaceZ = 1015, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 67 wkbTINZ = 1016, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 68 wkbTriangleZ = 1017, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 69 70 wkbPointM = 2001, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 71 wkbLineStringM = 2002, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 72 wkbPolygonM = 2003, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 73 wkbMultiPointM = 2004, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 74 wkbMultiLineStringM = 2005, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 75 wkbMultiPolygonM = 2006, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 76 wkbGeometryCollectionM = 2007, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 77 wkbCircularStringM = 2008, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 78 wkbCompoundCurveM = 2009, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 79 wkbCurvePolygonM = 2010, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 80 wkbMultiCurveM = 2011, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 81 wkbMultiSurfaceM = 2012, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 82 wkbCurveM = 2013, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 83 wkbSurfaceM = 2014, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 84 wkbPolyhedralSurfaceM = 2015, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 85 wkbTINM = 2016, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 86 wkbTriangleM = 2017, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 87 88 wkbPointZM = 3001, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 89 wkbLineStringZM = 3002, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 90 wkbPolygonZM = 3003, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 91 wkbMultiPointZM = 3004, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 92 wkbMultiLineStringZM = 3005, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 93 wkbMultiPolygonZM = 3006, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 94 wkbGeometryCollectionZM = 3007, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 95 wkbCircularStringZM = 3008, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 96 wkbCompoundCurveZM = 3009, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 97 wkbCurvePolygonZM = 3010, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 98 wkbMultiCurveZM = 3011, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 99 wkbMultiSurfaceZM = 3012, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 100 wkbCurveZM = 3013, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 101 wkbSurfaceZM = 3014, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 102 wkbPolyhedralSurfaceZM = 3015, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 103 wkbTINZM = 3016, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 104 wkbTriangleZM = 3017, /**< ISO SQL/MM Part 3. GDAL >= 2.1 */ 105 106 // add tests for M 107 #define wkbHasM(x) OGR_GT_HasM(x) 108 #define wkbSetM(x) OGR_GT_SetM(x) 109 110 OGRwkbGeometryType CPL_DLL OGR_GT_SetM( OGRwkbGeometryType eType ); 111 int CPL_DLL OGR_GT_HasM( OGRwkbGeometryType eType ); 112 113 114C++ API 115~~~~~~~ 116 117The property int nCoordinateDimension in class OGRGeometry will be 118replaced by int flags. It may have the following flags: 119 120:: 121 122 #define OGR_G_NOT_EMPTY_POINT 0x1 123 #define OGR_G_3D 0x2 124 #define OGR_G_MEASURED 0x4 125 #define OGR_G_IGNORE_MEASURED 0x8 126 127The "ignore" flag is needed internally for backwards compatibility. The 128flag OGR_G_NOT_EMPTY_POINT is used only to denote the emptiness of an 129OGRPoint object. 130 131Currently a hack to set nCoordDimension negative is used to denote an 132empty point. 133 134The removal of nCoordinateDimension may imply changes to drivers etc. 135which get or set it. 136 137The tests are 138 139:: 140 141 Is3D = flags & OGR_G_3D 142 IsMeasured = flags & OGR_G_MEASURED 143 144The setters and getters are implemented with \|= and &=. 145 146When any of these flags is set or unset, the corresponding data becomes 147invalid and may be discarded. 148 149Keep the following methods with original semantics, i.e., coordinate 150dimension is 2 or 3, but deprecate. There is some discrepancy in 151documentation. Their documentation says that they may return zero for 152empty points while in ogrpoint.cpp it says that negative nCoordDimension 153values are used for empty points and the getCoordinateDimension method 154of point returns absolute value of nCoordDimension - thus not zero. A 155fix to the doc is probably enough. 156 157:: 158 159 int getCoordinateDimension(); 160 void setCoordinateDimension(int nDimension); 161 void flattenTo2D() 162 163It is proposed to possibly add a new method to replace 164getCoordinateDimension. set3D and setMeasured would replace 165setCoordinateDimension and flattenTo2D. See below. 166 167class OGRGeometry: 168 169:: 170 171 //Possibly add methods (SF Common Architecture): 172 int Dimension(); // -1 for empty geometries (to denote undefined), 0 for points, 1 for curves, 2 for surfaces, max of components for collections 173 char *GeometryType(); // calls OGRToOGCGeomType (which needs to be enhanced) 174 175 //Add methods (SF Common Architecture) see above for implementation: 176 int CoordinateDimension(); // 2 if not 3D and not measured, 3 if 3D or measured, 4 if 3D and measured 177 OGRBoolean Is3D() const; 178 OGRBoolean IsMeasured() const; 179 180 //Add methods (non-standard; note the use of one method instead of second unset* method): 181 virtual void set3D(OGRBoolean bIs3D); 182 virtual void setMeasured(OGRBoolean bIsMeasured); 183 184 //Add now or later methods: 185 virtual OGRGeometry *LocateAlong(double mValue); 186 virtual OGRGeometry *LocateBetween(double mStart, double mEnd); 187 188 //Remove b3D from importPreambleFromWkb: it is not used, the flags are managed within the method. 189 190int CoordinateDimension() should have the new semantics. The method name 191in simple features documents actually is without prefix get. 192 193Whether set3D and setMeasured should affect the children geometries in a 194collection is an issue. Currently doc for setCoordinateDimension says 195"Setting the dimension of a geometry collection will affect the children 196geometries.", thus we have already committed to maintaining dimensions 197of children in collections. It is proposed that set3D and setMeasured 198either add or strip Z or M values to or from the geometry (including 199possible children). In general the strategy should be to follow the 200existing strategy regarding Z (i.e., to strip or add). 201 202Add property double m to class OGRPoint. Add constructor, getters, and 203setters for it. 204 205Add property double \*padfM to class OGRSimpleCurve. Add constructor, 206getters, and setters for it. New setters with postfix M are needed for 207XYM data since the object may be upgraded to XYZ from XY in setters. Add 208also methods RemoveM() and AddM() with similar semantics as Make3D and 209Make2D. 210 211Override methods set3D and setMeasured in those classes where 212setCoordinateDimension is overridden. 213 214Change the semantics of methods whose name begins with \_ and have a 215parameter "int b3D". The parameter will be "int coordinates", i.e., a 216flags like int, which tells about Z and M. 217 218C API 219~~~~~ 220 221ogr_core.h: 222 223:: 224 225 OGRwkbGeometryType CPL_DLL OGR_GT_SetM( OGRwkbGeometryType eType ); 226 int CPL_DLL OGR_GT_HasM( OGRwkbGeometryType eType ); 227 228The current behavior is that calling SetPoint on a geometry with 229coordinate dimension 2 upgrades the coordinate dimension 3. To keep 2D 230points 2D SetPoint_2D must be used. Thus we need separate functions for 231M and ZM geometries. The proposal is to use postfixes M and ZM, i.e., 232SetPointM, SetPointZM. Similarly for AddPoint. 233 234Currently there is no SetPoints_2D function. The doc at pabyZ param at 235SetPoints comments that "defaults to NULL for 2D objects" but that does 236not seem to be the case. See #6344. If that is fixed as written there, 237then only SetPointsZM is needed. 238 239GetPoint and GetPoints do not have a 2D version, so only \*ZM version is 240needed. 241 242ogr_api.h: 243 244:: 245 246 void CPL_DLL OGR_G_Is3D( OGRGeometryH ); 247 void CPL_DLL OGR_G_IsMeasured( OGRGeometryH ); 248 249 void CPL_DLL OGR_G_Set3D( OGRGeometryH, int ); 250 void CPL_DLL OGR_G_SetMeasured( OGRGeometryH, int ); 251 252 double CPL_DLL OGR_G_GetM( OGRGeometryH, int ); 253 254ogr_p.h (This is public header, so new functions are needed) 255 256:: 257 258 const char CPL_DLL * OGRWktReadPointsM( const char * pszInput, 259 OGRRawPoint **ppaoPoints, 260 double **ppadfZ, 261 double **ppadfM, 262 int * pnMaxPoints, 263 int * pnReadPoints ); 264 void CPL_DLL OGRMakeWktCoordinateM( char *, double, double, double, double, int ); // int = flags OGR_G_3D OGR_G_MEASURED 265 // Change the semantics of OGRReadWKBGeometryType: b3D is not used and the returned eGeometryType may may any valid type 266 267pggeometry.h is internal, so we can change the function prototype 268 269:: 270 271 void OGRCreateFromMultiPatchPart(OGRMultiPolygon *poMP, 272 OGRPolygon*& poLastPoly, 273 int nPartType, 274 int nPartPoints, 275 double* padfX, 276 double* padfY, 277 double* padfZ, 278 double* padfM); 279 280Use of padfM requires changes to openfilegdb driver. 281 282GEOS, filters, and other issues 283~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 284 285When a geometry with measures is sent to GEOS or used as a filter the M 286coordinate is ignored. 287 288LocateAlong and LocateBetween are the only standard methods, which use M 289but there could be others, which for example get the extent of M. Such 290are not intended to be added now but they can be added later. 291 292SWIG bindings (Python / Java / C# / Perl) changes 293------------------------------------------------- 294 295The new C API functions need to be exposed through swig. Further changes 296depend on whether the language bindings are aware of coordinates. At 297least Python and Perl are. 298 299The new geometry types will be included into the i files. 300 301Some new setters and getters are needed for M. Is3D, IsMeasured, Set3D 302and SetMeasured methods should be added. Also OGR_GT_HasM. 303 304Drivers 305------- 306 307Drivers that are probably affected by the C++ changes are at least 308(these use the CoordinateDimension API) pg, mssqlspatial, sqlite, db2, 309mysql, gml, pgdump, geojson, libkml, gpkg, wasp, gpx, filegdb, vfk, bna, 310dxf. 311 312The now deprecated CoordinateDimension API is proposed to be replaced 313with calls to \*3D and \*Measured. 314 315Once the support for M coordinates is in place the driver will advertise 316the support. 317 318Within the work of this RFC the support is built into memory, shape and 319pg drivers. Support for other drivers are left for further work. 320 321Utilities 322--------- 323 324There is a minimum requirement and new possibilities. 325 326ogrinfo: report measured geom type, report measures 327 328ogr2ogr: support measured geom types 329 330ogrlineref: seems to deal specifically with measures, needs more thought 331 332gdal_rasterize: measure could be used for the burn-in value 333 334gdal_contour: measure could be used as the "elevation" value 335 336gdal_grid: measure could be used as the "Z" value 337 338Documentation 339------------- 340 341All new methods/functions are documented. 342 343Test Suite 344---------- 345 346At least the initial tests will be done with Perl unit tests 347(swi/perl/t/measures-\*.t). Later autotest suite will be extended. 348Existing tests should not fail. 349 350Compatibility Issues 351-------------------- 352 353Many drivers (actually datasets and layers) which support measures need 354to have the support added. Support should be advertised using 355 356:: 357 358 #define ODsCMeasuredGeometries "MeasuredGeometries" 359 #define OLCMeasuredGeometries "MeasuredGeometries" 360 361The entry point for a creating a layer is CreateLayer method in 362GDALDataset. If the dataset does not support measured geometries it will 363strip the measured flag from the geometry type it gets as a parameter. 364This is in line with current behavior non linear geometry types and 365datasets not supporting them. 366 367ICreateLayer, which all drivers that have create layer capability 368implement, have geometry type as an argument. The method should call 369CPLError() with CPLE_NotSupported and return NULL if the driver does not 370support measures. Similarly for ICreateFeature and ISetFeature. 371 372The user-oriented API functions (CreateLayer, CreateFeature, and 373SetFeature) should (silently) strip out the measures before continuing 374to the I\* methods in drivers that do not support measures. This (side 375effect) may not be what is wanted in some usage scenarios but it would 376follow the pattern of what is already done with nonlinear geometries. 377This should be documented. 378 379An alternative would be to store M value(s) (or WKT or WKB) as attribute 380(scalar or vector, depending on the geometry type). 381 382Needs a decision. 383 384Some incompatibilities will necessarily be introduced. For example when 385the current XYM-as-XYZ hack in shape will be replaced by proper XYM. 386 387Related tickets 388--------------- 389 390`https://trac.osgeo.org/gdal/ticket/6063 <https://trac.osgeo.org/gdal/ticket/6063>`__ 391`https://trac.osgeo.org/gdal/ticket/6331 <https://trac.osgeo.org/gdal/ticket/6331>`__ 392 393Implementation 394-------------- 395 396The implementation will be done by Ari Jolma. 397 398The proposed implementation will be in 399`https://github.com/ajolma/GDAL-XYZM <https://github.com/ajolma/GDAL-XYZM>`__ 400 401Voting history 402-------------- 403 404+1 from Even, Tamas, Jukka and Daniel 405