navibyte / geospatial

Geospatial data structures, tools and utilities for Dart and Flutter.
Other
51 stars 5 forks source link

Bug: special case POINT EMPTY encoding x and y as double.nan in WKB encoder / byte writer #225

Closed navispatial closed 3 months ago

navispatial commented 3 months ago

There is a special case test:

      // https://trac.osgeo.org/geos/ticket/1005
      // https://github.com/OSGeo/gdal/issues/2472
      _testEncodeToBytesBase64(
        Endian.little,
        'AQEAAAAAAAAAAAD4fwAAAAAAAPh/',
        // hex: 0101000000000000000000F87F000000000000F87F
        //   IEEE specifies NaN in float64 as (big endian) 7ff80000 00000000
        //   in Dart it seems that (using ByteData) need to write `-double.nan`
        //   this is handled by wkb encoder / byte writer util....
        'POINT EMPTY',
        [
          (writer) => writer.emptyGeometry(Geom.point),
          (writer) => writer.point([double.nan, double.nan].xy),
          (writer) => writer.point([-double.nan, -double.nan].xy),
        ],
      );

This has succeed on geobase 1.0.0 tests in previous Dart SDKs but with latest Dart 3.3 SDK it seems that double.nan is written using dart:typed_data in different way.

Utility class ByteWriter had special cases (nanEncodedAsNegative):

  /// Writes [value] as four bytes (IEEE 754 single-precision floating-point).
  ///
  /// Uses the parameter [endian] if non-null, otherwise uses `this.endian`.
  ///
  /// See `ByteData.setFloat32` from `dart:typed_data` for reference.
  ///
  /// See also configuration parameter [nanEncodedAsNegative].
  void writeFloat32(double value, [Endian? endian]) {
    _reserve(4);
    _chunk.setFloat32(
      _offset,
      value.isNaN ? -double.nan : value,
      nanEncodedAsNegative && value.isNaN ? -double.nan : value,
      endian ?? this.endian,
    );
    _offset += 4;
  }

  /// Writes [value] as eight bytes (IEEE 754 double-precision floating-point).
  ///
  /// Uses the parameter [endian] if non-null, otherwise uses `this.endian`.
  ///
  /// See `ByteData.setFloat64` from `dart:typed_data` for reference.
  ///
  /// See also configuration parameter [nanEncodedAsNegative].
  void writeFloat64(double value, [Endian? endian]) {
    _reserve(8);
    _chunk.setFloat64(
      _offset,
      nanEncodedAsNegative && value.isNaN ? -double.nan : value,
      endian ?? this.endian,
    );
    _offset += 8;
  }

That were used on WKBEncoder:

  @override
  void emptyGeometry(Geom type, {String? name}) {
    // type for coordinates
    const coordType = Coords.xy;

    switch (type) {
      case Geom.point:
        // this is a special case => https://trac.osgeo.org/geos/ticket/1005
        //                           https://trac.osgeo.org/postgis/ticket/3181
        //                           https://github.com/OSGeo/gdal/issues/2472
        // write only x and y as double.nan
        // that is POINT(NaN NaN) is considered POINT EMPTY, or something..
        // Note: negative NaN (whatever it is) is needed to get same output in
        //       bytes as those OSGEO related (reliable?) sources
        //       (thats why buffer is create with nanEncodedAsNegative: true)
        _writeGeometryHeader(type, Coords.xy);
        _buffer
          ..writeFloat64(double.nan)
          ..writeFloat64(double.nan);
        break;
      case Geom.lineString:
      case Geom.polygon:
      case Geom.multiPoint:
      case Geom.multiLineString:
      case Geom.multiPolygon:
      case Geom.geometryCollection:
        // write geometry with 0 elements (points, rings, geometries, etc.)
        _writeGeometryHeader(type, coordType);
        _buffer.writeUint32(0);
        break;
    }
  }

Removing special case for negative NaN and just writing a positive NaN whenever a NaN value (negative or positive) seems to fix...

navispatial commented 3 months ago

Implemented in geobase 1.0.1