Skip to content

Commit

Permalink
OGRLayer::CreateFeature()/SetFeature(): honor OGR_APPLY_GEOM_SET_PREC…
Browse files Browse the repository at this point in the history
…ISION=YES configuration option to call OGRGeometry::SetPrecision()
  • Loading branch information
rouault committed Mar 6, 2024
1 parent 4e8862e commit 110fc79
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 98 deletions.
29 changes: 29 additions & 0 deletions autotest/ogr/ogr_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3055,6 +3055,35 @@ def test_ogr_csv_geom_coord_precision(tmp_vsimem, geometry_format):
assert b"1.23457,2.34568,9.877" in data


###############################################################################
# Test geometry coordinate precision support


@pytest.mark.require_geos
def test_ogr_csv_geom_coord_precision_OGR_APPLY_GEOM_SET_PRECISION(tmp_vsimem):

filename = str(tmp_vsimem / "test.csv")
ds = gdal.GetDriverByName("CSV").Create(filename, 0, 0, 0, gdal.GDT_Unknown)
geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown)
prec = ogr.CreateGeomCoordinatePrecision()
prec.Set(0.5, 0, 0)
geom_fld.SetCoordinatePrecision(prec)
lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld, ["GEOMETRY=AS_WKT"])
f = ogr.Feature(lyr.GetLayerDefn())
f.SetGeometry(
ogr.CreateGeometryFromWkt("POLYGON((0 0,0.5 0.4,1 0,1 1,0.5 0.6,0 1,0 0))")
)
with gdaltest.config_option("OGR_APPLY_GEOM_SET_PRECISION", "YES"):
lyr.CreateFeature(f)
ds.Close()

f = gdal.VSIFOpenL(filename, "rb")
assert f
data = gdal.VSIFReadL(1, 10000, f)
gdal.VSIFCloseL(f)
assert b"MULTIPOLYGON" in data


###############################################################################


Expand Down
166 changes: 91 additions & 75 deletions doc/source/user/configoptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,88 @@ General options
Sets the resampling algorithm to be used when reading from a raster
into a buffer with different dimensions from the source region.

- .. config:: CPL_VSIL_ZIP_ALLOWED_EXTENSIONS
:choices: <comma-separated list>

Add to zip FS handler default extensions array (zip, kmz, dwf, ods, xlsx)
additional extensions listed in :config:`CPL_VSIL_ZIP_ALLOWED_EXTENSIONS` config
option.

- .. config:: CPL_VSIL_DEFLATE_CHUNK_SIZE
:default: 1 M

- .. config:: GDAL_DISABLE_CPLLOCALEC
:choices: YES, NO
:default: NO

If set to YES (default is NO) this option will disable the normal behavior of
the CPLLocaleC class which forces the numeric locale to "C" for selected chunks
of code using the setlocale() call. Behavior of setlocale() in multi-threaded
applications may be undependable but use of this option may result in problem
formatting and interpreting numbers properly.

- .. config:: GDAL_FILENAME_IS_UTF8
:choices: YES, NO
:default: YES

This option only has an effect on Windows systems (using
cpl_vsil_win32.cpp). If set to "NO" then filenames passed
to functions like :cpp:func:`VSIFOpenL` will be passed on directly to CreateFile()
instead of being converted from UTF-8 to wchar_t and passed to
CreateFileW(). This effectively restores the pre-GDAL1.8 behavior for
handling filenames on Windows and might be appropriate for applications that
treat filenames as being in the local encoding.

- .. config:: GDAL_MAX_BAND_COUNT
:choices: <integer>
:default: 65536

Defines the maximum number of bands to read from a single dataset.

- .. config:: GDAL_XML_VALIDATION
:choices: YES, NO
:default: YES

Determines whether XML content should be validated against an XSD, with
non-conformities reported as warnings.

- .. config:: GDAL_GEOREF_SOURCES
:since: 2.2

Determines the order in which potential georeferencing sources are
scanned. Value should be a comma-separated list of sources in order of
decreasing priority. The set of sources recognized by this option is
driver-specific.

- .. config:: GDAL_OVR_PROPAGATE_NODATA
:choices: YES, NO
:default: NO
:since: 2.2

When computing the value of an overview pixel, determines whether a
single NODATA value should cause the overview pixel to be set to NODATA
(``YES``), or whether the NODATA values should be simply ignored
(``NO``). This configuration option is not supported for all resampling
algorithms/data types.


- .. config:: USE_RRD
:choices: YES, NO
:default: NO

Used by :source_file:`gcore/gdaldefaultoverviews.cpp`

Can be set to YES to use Erdas Imagine format (.aux) as overview format. See
:program:`gdaladdo` documentation.

- .. config:: PYTHONSO

Location of Python shared library file, e.g. ``pythonX.Y[...].so/.dll``.


Vector related options
^^^^^^^^^^^^^^^^^^^^^^

- .. config:: OGR_ARC_STEPSIZE
:choices: <degrees>
:default: 4
Expand Down Expand Up @@ -461,7 +543,6 @@ General options
are present, a GeometryCollection will be returned.



- .. config:: OGR_SQL_LIKE_AS_ILIKE
:choices: YES, NO
:default: NO
Expand All @@ -483,83 +564,18 @@ General options
Set to NO to preserve the content, but beware that the resulting XML file will
not be valid and will require manual edition of the encoding in the XML header.

- .. config:: CPL_VSIL_ZIP_ALLOWED_EXTENSIONS
:choices: <comma-separated list>

Add to zip FS handler default extensions array (zip, kmz, dwf, ods, xlsx)
additional extensions listed in :config:`CPL_VSIL_ZIP_ALLOWED_EXTENSIONS` config
option.

- .. config:: CPL_VSIL_DEFLATE_CHUNK_SIZE
:default: 1 M

- .. config:: GDAL_DISABLE_CPLLOCALEC
:choices: YES, NO
:default: NO

If set to YES (default is NO) this option will disable the normal behavior of
the CPLLocaleC class which forces the numeric locale to "C" for selected chunks
of code using the setlocale() call. Behavior of setlocale() in multi-threaded
applications may be undependable but use of this option may result in problem
formatting and interpreting numbers properly.

- .. config:: GDAL_FILENAME_IS_UTF8
:choices: YES, NO
:default: YES

This option only has an effect on Windows systems (using
cpl_vsil_win32.cpp). If set to "NO" then filenames passed
to functions like :cpp:func:`VSIFOpenL` will be passed on directly to CreateFile()
instead of being converted from UTF-8 to wchar_t and passed to
CreateFileW(). This effectively restores the pre-GDAL1.8 behavior for
handling filenames on Windows and might be appropriate for applications that
treat filenames as being in the local encoding.

- .. config:: GDAL_MAX_BAND_COUNT
:choices: <integer>
:default: 65536

Defines the maximum number of bands to read from a single dataset.

- .. config:: GDAL_XML_VALIDATION
:choices: YES, NO
:default: YES

Determines whether XML content should be validated against an XSD, with
non-conformities reported as warnings.

- .. config:: GDAL_GEOREF_SOURCES
:since: 2.2

Determines the order in which potential georeferencing sources are
scanned. Value should be a comma-separated list of sources in order of
decreasing priority. The set of sources recognized by this option is
driver-specific.

- .. config:: GDAL_OVR_PROPAGATE_NODATA
- .. config:: OGR_APPLY_GEOM_SET_PRECISION
:choices: YES, NO
:default: NO
:since: 2.2

When computing the value of an overview pixel, determines whether a
single NODATA value should cause the overview pixel to be set to NODATA
(``YES``), or whether the NODATA values should be simply ignored
(``NO``). This configuration option is not supported for all resampling
algorithms/data types.


- .. config:: USE_RRD
:choices: YES, NO
:default: NO

Used by :source_file:`gcore/gdaldefaultoverviews.cpp`

Can be set to YES to use Erdas Imagine format (.aux) as overview format. See
:program:`gdaladdo` documentation.

- .. config:: PYTHONSO
:since: 3.9

Location of Python shared library file, e.g. ``pythonX.Y[...].so/.dll``.
By default, when a geometry coordinate precision is set on a geometry field
definition and a driver honors the GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION
capability, geometries passed to :cpp:func:`OGRLayer::CreateFeature` and OGRLayer::SetFeature() are assumed to be compatible of the coordinate precision. That is they are
assumed to be valid once their coordinates are rounded to it. If it might
not be the case, set this configuration option to YES before calling
CreateFeature() or SetFeature() to force :cpp:func:`OGRGeometry::SetPrecision`
to be called on the passed geometries.


Networking options
Expand Down
39 changes: 33 additions & 6 deletions gcore/gdaldataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4963,6 +4963,22 @@ OGRLayer *GDALDataset::CreateLayer(const char *pszName,
\brief This method attempts to create a new layer on the dataset with the
indicated name and geometry field definition.
When poGeomFieldDefn is not null, most drivers should honor
poGeomFieldDefn->GetType() and poGeomFieldDefn->GetSpatialRef().
Drivers that honor poGeomFieldDefn->GetCoordinatePrecision() will declare the
GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability. Drivers may honor
poGeomFieldDefn->GetNameRef() and poGeomFieldDefn->IsNullable(), but there are
very few currently.
Note that even if a geometry coordinate precision is set and a driver honors the
GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability, geometries passed to
OGRLayer::CreateFeature() and OGRLayer::SetFeature() are assumed to be compatible
of the coordinate precision. That is they are assumed to be valid once their
coordinates are rounded to it. If it might not be the case, the user may set
the OGR_APPLY_GEOM_SET_PRECISION configuration option before calling CreateFeature()
or SetFeature() to force the OGRGeometry::SetPrecision() method to be called on
the passed geometries.
The papszOptions argument
can be used to control driver specific creation options. These options are
normally documented in the format specific documentation.
Expand All @@ -4980,12 +4996,7 @@ This method is the same as the C function GDALDatasetCreateLayerFromGeomFieldDef
@param pszName the name for the new layer. This should ideally not
match any existing layer on the datasource.
@param poGeomFieldDefn the geometry field definition to use for the new layer,
or NULL if there is no geometry field. Most drivers should honor
poGeomFieldDefn->GetType() and poGeomFieldDefn->GetSpatialRef().
Drivers that honor poGeomFieldDefn->GetCoordinatePrecision() will declare the
GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability. Drivers may honor
poGeomFieldDefn->GetNameRef() and poGeomFieldDefn->IsNullable(), but there are
very few currently.
or NULL if there is no geometry field.
@param papszOptions a StringList of name=value options. Options are driver
specific.
Expand Down Expand Up @@ -5148,6 +5159,22 @@ OGRLayerH GDALDatasetCreateLayer(GDALDatasetH hDS, const char *pszName,
\brief This function attempts to create a new layer on the dataset with the
indicated name and geometry field.
When poGeomFieldDefn is not null, most drivers should honor
poGeomFieldDefn->GetType() and poGeomFieldDefn->GetSpatialRef().
Drivers that honor poGeomFieldDefn->GetCoordinatePrecision() will declare the
GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability. Drivers may honor
poGeomFieldDefn->GetNameRef() and poGeomFieldDefn->IsNullable(), but there are
very few currently.
Note that even if a geometry coordinate precision is set and a driver honors the
GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability, geometries passed to
OGRLayer::CreateFeature() and OGRLayer::SetFeature() are assumed to be compatible
of the coordinate precision. That is they are assumed to be valid once their
coordinates are rounded to it. If it might not be the case, the user may set
the OGR_APPLY_GEOM_SET_PRECISION configuration option before calling CreateFeature()
or SetFeature() to force the OGRGeometry::SetPrecision() method to be called on
the passed geometries.
The papszOptions argument can be used to control driver specific creation
options. These options are normally documented in the format specific
documentation.
Expand Down
86 changes: 69 additions & 17 deletions ogr/ogrsf_frmts/generic/ogrlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -655,28 +655,80 @@ OGRFeatureH OGR_L_GetNextFeature(OGRLayerH hLayer)

void OGRLayer::ConvertGeomsIfNecessary(OGRFeature *poFeature)
{
const bool bSupportsCurve = CPL_TO_BOOL(TestCapability(OLCCurveGeometries));
const bool bSupportsM = CPL_TO_BOOL(TestCapability(OLCMeasuredGeometries));
if (!bSupportsCurve || !bSupportsM)
if (!m_poPrivate->m_bConvertGeomsIfNecessaryAlreadyCalled)
{
int nGeomFieldCount = GetLayerDefn()->GetGeomFieldCount();
for (int i = 0; i < nGeomFieldCount; i++)
// One time initialization
m_poPrivate->m_bConvertGeomsIfNecessaryAlreadyCalled = true;
m_poPrivate->m_bSupportsCurve =
CPL_TO_BOOL(TestCapability(OLCCurveGeometries));
m_poPrivate->m_bSupportsM =
CPL_TO_BOOL(TestCapability(OLCMeasuredGeometries));
if (CPLTestBool(
CPLGetConfigOption("OGR_APPLY_GEOM_SET_PRECISION", "FALSE")))
{
OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
if (poGeom != nullptr &&
(!bSupportsM && OGR_GT_HasM(poGeom->getGeometryType())))
const auto poFeatureDefn = GetLayerDefn();
const int nGeomFieldCount = poFeatureDefn->GetGeomFieldCount();
for (int i = 0; i < nGeomFieldCount; i++)
{
poGeom->setMeasured(FALSE);
const double dfXYResolution = poFeatureDefn->GetGeomFieldDefn(i)
->GetCoordinatePrecision()
.dfXYResolution;
if (dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN &&
OGRGeometryFactory::haveGEOS())
{
m_poPrivate->m_bApplyGeomSetPrecision = true;
break;
}
}
if (poGeom != nullptr &&
(!bSupportsCurve &&
OGR_GT_IsNonLinear(poGeom->getGeometryType())))
}
}

if (!m_poPrivate->m_bSupportsCurve || !m_poPrivate->m_bSupportsM ||
m_poPrivate->m_bApplyGeomSetPrecision)
{
const auto poFeatureDefn = GetLayerDefn();
const int nGeomFieldCount = poFeatureDefn->GetGeomFieldCount();
for (int i = 0; i < nGeomFieldCount; i++)
{
OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
if (poGeom)
{
OGRwkbGeometryType eTargetType =
OGR_GT_GetLinear(poGeom->getGeometryType());
poFeature->SetGeomFieldDirectly(
i, OGRGeometryFactory::forceTo(poFeature->StealGeometry(i),
eTargetType));
if (!m_poPrivate->m_bSupportsM &&
OGR_GT_HasM(poGeom->getGeometryType()))
{
poGeom->setMeasured(FALSE);
}

if (!m_poPrivate->m_bSupportsCurve &&
OGR_GT_IsNonLinear(poGeom->getGeometryType()))
{
OGRwkbGeometryType eTargetType =
OGR_GT_GetLinear(poGeom->getGeometryType());
poGeom = OGRGeometryFactory::forceTo(
poFeature->StealGeometry(i), eTargetType);
poFeature->SetGeomFieldDirectly(i, poGeom);
}

if (m_poPrivate->m_bApplyGeomSetPrecision)
{
const double dfXYResolution =
poFeatureDefn->GetGeomFieldDefn(i)
->GetCoordinatePrecision()
.dfXYResolution;
if (dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN &&
!poGeom->hasCurveGeometry())
{
auto poNewGeom = poGeom->SetPrecision(dfXYResolution,
/* nFlags = */ 0);
if (poNewGeom)
{
poFeature->SetGeomFieldDirectly(i, poNewGeom);
poGeom = poNewGeom;
}
}
}

CPL_IGNORE_RET_VAL(poGeom);
}
}
}
Expand Down
Loading

0 comments on commit 110fc79

Please sign in to comment.