navibyte / geospatial

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

Redesigned PositionData supporting positions from coordinate value array and position objects #200

Closed navispatial closed 9 months ago

navispatial commented 10 months ago

See also #199.

PositionData shall be deprecated.

Planned redesigned PositionData class definition as a new type PositionSeries:

/// A fixed-length (and random-access) view to a series of positions.
///
/// Implementations of this abstract class can use at least two different
/// structures to store coordinate values of positions contained in a series:
/// * A list of [Position] objects (each object contain x and y coordinates, and
///   optionally z and m too).
/// * A list of [double] values as a flat structure. For example for a double
///   list could contain coordinates like [x0, y0, z0, x1, y1, z1, x2, y2, z2]
///   that represents three positions each with x, y and z coordinates.
///
/// It's also possible to create a position data instance using factory methods
/// [PositionSeries.view] and [PositionSeries.parse] that create an instance
/// storing coordinate values of positions in a double array. The factory
/// [PositionSeries.from] creates an instance storing positions objects in an
/// array.
///
/// See [Position] for description about supported coordinate values.
///
/// For [PositionSeries] and sub classes equality by `operator ==` and
/// `hashCode` is not testing coordinate values for contained positions. Methods
/// [equalsCoords], [equals2D] and [equals3D] should be used to test coordinate
/// values between two [PositionSeries] instances.
abstract class PositionSeries implements Positionable {
  /// A series of positions as a view backed by [source] containing coordinate
  /// values of positions.
  ///
  /// The [source] collection contains coordinate values of positions as a flat
  /// structure. For example for `Coords.xyz` the first three coordinate values
  /// are x, y and z of the first position, the next three coordinate values are
  /// x, y and z of the second position, and so on.
  ///
  /// The `type.coordinateDimension` (either 2, 3 or 4) property defines the
  /// number of coordinate values for each position. The number of positions
  /// contained by the view is calculated as
  /// `source.length ~/ type.coordinateDimension`. If there are zero values or
  /// less coordinate values than `type.coordinateDimension`, then the view is
  /// considered empty.
  ///
  /// An iterable collection of [source] may be represented by a [List] or any
  /// [Iterable] with efficient `length` and `elementAt` implementations. A lazy
  /// iterable with a lot of coordinate values may produce very poor
  /// performance.
  ///
  /// See [Position] for description about supported coordinate values.
  factory PositionSeries.view(Iterable<double> source, {required Coords type});

  /// A series of positions as a view backed by [source] containing [Position]
  /// objects.
  ///
  /// If given [type] is null then the coordinate type of [source] positions is
  /// resolved from those positions (a type returned is such that it's valid
  /// for all positions).
  ///
  /// An iterable collection of [source] may be represented by a [List] or any
  /// [Iterable] with efficient `length` and `elementAt` implementations. A lazy
  /// iterable with a lot of position objects may produce very poor performance.
  ///
  /// See [Position] for description about supported coordinate values.
  factory PositionSeries.from(Iterable<Position> source, {Coords? type});

  /// Parses a series of positions from [text] containing coordinate values of
  /// positions.
  ///
  /// Coordinate values in [text] are separated by [delimiter].
  ///
  /// See [Position] for description about supported coordinate values.
  ///
  /// Throws FormatException if coordinates are invalid.
  factory PositionSeries.parse(
    String text, {
    Pattern? delimiter = ',',
    Coords type = Coords.xy,
  });

  /// The number of positions in this series.
  int get length;

  /// Returns true if this series has no positions.
  bool get isEmpty;

  /// Returns true if this series has at least one position.
  bool get isNotEmpty;

  /// All positions in this series as an iterable.
  ///
  /// See also [positionsAs] that allow typing position object as subtypes of
  /// [Position].
  Iterable<Position> get positions;

  /// All positions in this series as an iterable of positions typed as [R]
  /// using [to] factory.
  ///
  /// See also [positions] that always returns position objects as [Position].
  Iterable<R> positionsAs<R extends Position>({
    required CreatePosition<R> to,
  });

  /// The position at the given index.
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  ///
  /// See also [get] that allow typing the position object as subtypes of
  /// [Position].
  Position operator [](int index);

  /// The position at the given index as an object of [R] using [to] factory.
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  ///
  /// Examples when `series` is an instance of [PositionSeries]:
  ///
  /// ```dart
  /// // get a position at index 3 as a `Projected` position
  /// series.get(3, to: Projected.create);
  ///
  /// // get a position at index 3 as a `Geographic` position
  /// series.get(3, to: Geographic.create);
  /// ```
  R get<R extends Position>(
    int index, {
    required CreatePosition<R> to,
  });

  /// The first position or null (if empty collection).
  Position? get firstOrNull;

  /// The last position or null (if empty collection).
  Position? get lastOrNull;

  /// The `x` coordinate of the position at the given index.
  ///
  /// For geographic coordinates x represents *longitude*.
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  double x(int index);

  /// The `y` coordinate of the position at the given index.
  ///
  /// For geographic coordinates y represents *latitude*.
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  double y(int index);

  /// The `z` coordinate of the position at the given index.
  ///
  /// Returns zero if z is not available for a valid index. You can also use
  /// [optZ] that returns z coordinate as a nullable value.
  ///
  /// For geographic coordinates z represents *elevation* or *altitude*.
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  double z(int index);

  /// The `z` coordinate of the position at the given index.
  ///
  /// Returns null if z is not available for a valid index.
  ///
  /// For geographic coordinates z represents *elevation* or *altitude*.
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  double? optZ(int index);

  /// The `m` coordinate of the position at the given index.
  ///
  /// Returns zero if m is not available for a valid index. You can also use
  /// [optM] that returns m coordinate as a nullable value.
  ///
  /// `m` represents a measurement or a value on a linear referencing system
  /// (like time).
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  double m(int index);

  /// The `m` coordinate of the position at the given index.
  ///
  /// Returns null if m is not available for a valid index.
  ///
  /// `m` represents a measurement or a value on a linear referencing system
  /// (like time).
  ///
  /// The index must be a valid index in this series; `0 <= index < length`.
  double? optM(int index);

  /// Coordinate values of all positions in this series as an iterable.
  ///
  /// Each position contains 2, 3 or 4 coordinate values indicated by [type] of
  /// this series.
  ///
  /// For example if data contains positions (x: 1.0, y: 1.1), (x: 2.0, y: 2.1),
  /// and (x: 3.0, y: 3.1), then a returned iterable would be
  /// `[1.0, 1.1, 2.0, 2.1, 3.0, 3.1]`.
  ///
  /// For projected or cartesian coordinates, the coordinate ordering is:
  /// (x, y), (x, y, z), (x, y, m) or (x, y, z, m).
  ///
  /// For geographic coordinates, the coordinate ordering is:
  /// (lon, lat), (lon, lat, elev), (lon, lat, m) or (lon, lat, elev, m).
  ///
  /// See also [valuesByType] that returns coordinate values of all positions
  /// according to a given coordinate type.
  Iterable<double> get values;

  /// Coordinate values of all positions in this series as an iterable.
  ///
  /// Each position contains 2, 3 or 4 coordinate values indicated by given
  /// [type].
  ///
  /// See [values] (that returns coordinate values according to the coordinate
  /// type of this bounding box) for description of possible return values.
  Iterable<double> valuesByType(Coords type);

  /// True if the first and last position equals in 2D.
  bool get isClosed;

  /// True if the first and last position equals in 2D within [toleranceHoriz].
  bool isClosedBy([double toleranceHoriz = defaultEpsilon]);

  /// Returns true if this and [other] contain exactly same coordinate values
  /// (or both are empty) in the same order and with the same coordinate type.
  bool equalsCoords(PositionSeries other);

  /// True if this series of positions equals with [other] by testing 2D
  /// coordinates of all positions (that must be in same order in both series).
  ///
  /// Returns false if this or [other] is empty ([isEmpty] is true).
  ///
  /// Differences on 2D coordinate values (ie. x and y, or lon and lat) between
  /// this and [other] must be within [toleranceHoriz].
  ///
  /// Tolerance values must be positive (>= 0.0).
  bool equals2D(
    PositionSeries other, {
    double toleranceHoriz = defaultEpsilon,
  });

  /// True if this series of positions equals with [other] by testing 3D
  /// coordinates of all positions (that must be in same order in both views).
  ///
  /// Returns false if this or [other] is empty ([isEmpty] is true).
  ///
  /// Returns false if this or [other] do not contain 3D coordinates.
  ///
  /// Differences on 2D coordinate values (ie. x and y, or lon and lat) between
  /// this and [other] must be within [toleranceHoriz].
  ///
  /// Differences on vertical coordinate values (ie. z or elev) between
  /// this and [other] must be within [toleranceVert].
  ///
  /// Tolerance values must be positive (>= 0.0).
  bool equals3D(
    PositionSeries other, {
    double toleranceHoriz = defaultEpsilon,
    double toleranceVert = defaultEpsilon,
  });
}
navispatial commented 9 months ago

Implemented in geobase 0.6.0