Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
navispatial opened this issue Sep 7, 2023 · 1 comment
Labels
enhancement New feature or request 🌐 geobase Related to the code package "geobase" refactoring

Comments

@navispatial
Copy link
Member

navispatial commented Sep 7, 2023

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 navispatial added enhancement New feature or request refactoring 🌐 geobase Related to the code package "geobase" labels Sep 7, 2023
navispatial added a commit that referenced this issue Sep 10, 2023
navispatial added a commit that referenced this issue Sep 12, 2023
navispatial added a commit that referenced this issue Sep 16, 2023
navispatial added a commit that referenced this issue Sep 30, 2023
@navispatial
Copy link
Member Author

Implemented in geobase 0.6.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request 🌐 geobase Related to the code package "geobase" refactoring
Projects
None yet
Development

No branches or pull requests

1 participant