navibyte / geospatial

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

Transform and filter positions and position series #218

Closed navispatial closed 11 months ago

navispatial commented 11 months ago

Generic interface to transform and filter positions and position series.

There is already typedefs or mixins used for transforming and projecting positions, positions series and other geometries:

/// A function to transform the [source] position to a position of [T] using
/// [to] as a factory.
typedef TransformPosition = T Function<T extends Position>(
  Position source, {
  required CreatePosition<T> to,
});

/// A mixin defining an interface for (geospatial) projections.
///
/// A class that implements this mixin may provide for example a map projection
/// from geographic positions to projected positions, or an inverse projection
/// (or an "unprojection") from projected positions to geographic positions.
/// Both are called simply "projections" here.
mixin Projection {
  /// Projects the [source] position to a position of [T] using [to] as a
  /// factory.
  ///
  /// Throws FormatException if cannot project.
  T project<T extends Position>(
    Position source, {
    required CreatePosition<T> to,
  });

  /// Projects positions from [source] and returns a list of projected values.
  ///
  /// Use the required [type] to explicitely specify the type of coordinates.
  ///
  /// The [source] array 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 length of the [target] array, when given, must be exactly same as the
  /// length of the [source] array, and [target] must be a mutable list. If
  /// [target] is null, then a new list instance is created.
  ///
  /// Throws FormatException if cannot project.
  List<double> projectCoords(
    Iterable<double> source, {
    List<double>? target,
    required Coords type,
  });
}

These are used in Position:

  /// Projects this position to another position using [projection].
  ///
  /// Subtypes may specify a more accurate position type for the returned object
  /// (for example a *geographic* position would return a *projected* position
  /// when forward-projecting, and other way when inverse-projecting).
  @override
  Position project(Projection projection);

  /// Returns a position transformed from this using [transform].
  ///
  /// As an example a transform function could be:
  ///
  /// ```dart
  /// /// A sample transform function for positions that translates `x` by 5.0,
  /// /// scales `y` by 2.0, keeps `z` intact (null or a value), and ensures
  /// /// `m` is cleared.
  /// T _sampleTransform<T extends Position>(
  ///   Position source, {
  ///   required CreatePosition<T> to,
  /// }) =>
  ///     // call factory to create a transformed position
  ///     to.call(
  ///       x: source.x + 5.0, // translate x by 5.0
  ///       y: source.y * 2.0, // scale y by 2.0
  ///       z: source.optZ, // copy z value from source (null or a value)
  ///       m: null, // set m null even if source has null
  ///     );
  /// ```
  ///
  /// Then this transform function could be applied like this:
  ///
  /// ```dart
  /// // create a position X and Y coordinate values
  /// final position1 = [10.0, 11.0].xy;
  ///
  /// // transform it
  /// final transformed = position1.transform(_sampleTransform);
  ///
  /// // in this case the result would contains same coordinate values as this
  /// final position2 = [15.0, 22.0].xy;
  /// ```
  Position transform(TransformPosition transform);

And in PositionSeries:

  /// Projects this series of positions to another series using [projection].
  @override
  PositionSeries project(Projection projection);

  /// Returns a position series with all positions transformed using [transform].
  ///
  /// As an example a transform function could be:
  ///
  /// ```dart
  /// /// A sample transform function for positions that translates `x` by 5.0,
  /// /// scales `y` by 2.0, keeps `z` intact (null or a value), and ensures
  /// /// `m` is cleared.
  /// T _sampleTransform<T extends Position>(
  ///   Position source, {
  ///   required CreatePosition<T> to,
  /// }) =>
  ///     // call factory to create a transformed position
  ///     to.call(
  ///       x: source.x + 5.0, // translate x by 5.0
  ///       y: source.y * 2.0, // scale y by 2.0
  ///       z: source.optZ, // copy z value from source (null or a value)
  ///       m: null, // set m null even if source has null
  ///     );
  /// ```
  ///
  /// Then this transform function could be applied like this:
  ///
  /// ```dart
  /// // create a position series object with three XY positions
  /// final series1 = [
  ///   [10.0, 11.0].xy,
  ///   [20.0, 21.0].xy,
  ///   [30.0, 31.0].xy,
  /// ].series();
  ///
  /// // transform it
  /// final transformed = series1.transform(_sampleTransform);
  ///
  /// // in this case the result would contains same coordinate values as this
  /// final series2 = [
  ///   [15.0, 22.0].xy,
  ///   [25.0, 42.0].xy,
  ///   [35.0, 62.0].xy,
  /// ].series()
  /// ```
  PositionSeries transform(TransformPosition transform) 

Projections are meant for geospatial projections (even between geographic and projected coordinates).

Transforms can be used to transform an position to another position, or an position series with N positions to another series with N positions.

These do no fit to use case when positions series of N positions should be filtered to a seris of M positions (M different to N).

So a new typedef would be defined:

/// A function to filter the [source] position to an iterable of positions of [T]
/// using [to] as a factory.
///
/// It's also allowed to return zero positions if the [source] position should
/// be filtered out. 
typedef FilterPosition = Iterable<T> Function<T extends Position>(
  Position source, {
  required CreatePosition<T> to,
});

In Position this would be used:

  /// Filters this position as an iterable of positions of using [filter].
  Iterable<Position> filter(FilterPosition filter) =>

And in PositionSeries:

  /// Returns a position series with all positions transformed using [filter].
  PositionSeries filter(FilterPosition filter);
navispatial commented 11 months ago

Implemented in geobase 1.0.0.