From 4d6c7d5218556bd434a6c976ea392a743cea5cb3 Mon Sep 17 00:00:00 2001 From: navibyte <88932567+navispatial@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:24:36 +0300 Subject: [PATCH] feat(geobase): recalculate bounds in project if bounds was available #141 --- .../geobase/lib/src/utils/bounds_builder.dart | 10 ++- .../vector_data/model/bounded/bounded.dart | 7 +- .../vector_data/model/feature/feature.dart | 66 ++++++++++++++----- .../model/feature/feature_collection.dart | 29 +++++--- .../model/feature/feature_object.dart | 5 +- .../vector_data/model/geometry/geometry.dart | 5 +- .../model/geometry/geometry_collection.dart | 27 ++++++-- .../model/geometry/linestring.dart | 17 ++++- .../model/geometry/multi_linestring.dart | 24 +++++-- .../model/geometry/multi_point.dart | 24 +++++-- .../model/geometry/multi_polygon.dart | 40 +++++++---- .../vector_data/model/geometry/polygon.dart | 21 ++++-- .../test/projections/projection_sample.dart | 17 ++++- .../vector_data_projections_test.dart | 18 +++++ 14 files changed, 235 insertions(+), 75 deletions(-) diff --git a/dart/geobase/lib/src/utils/bounds_builder.dart b/dart/geobase/lib/src/utils/bounds_builder.dart index 7b78d582..fd2f855d 100644 --- a/dart/geobase/lib/src/utils/bounds_builder.dart +++ b/dart/geobase/lib/src/utils/bounds_builder.dart @@ -38,7 +38,7 @@ class BoundsBuilder { Iterable? arrays, Iterable? positions, required Coords type, - bool calculateChilds = false, + bool recalculateChilds = false, }) { if (item == null && (collection == null || collection.isEmpty) && @@ -50,7 +50,9 @@ class BoundsBuilder { final builder = BoundsBuilder(type); if (item != null) { - final bounds = calculateChilds ? item.calculateBounds() : item.bounds; + final bounds = recalculateChilds || item.bounds == null + ? item.calculateBounds() + : item.bounds; if (bounds != null) { builder.addBounds(bounds); } @@ -58,7 +60,9 @@ class BoundsBuilder { if (collection != null) { for (final elem in collection) { - final bounds = calculateChilds ? elem.calculateBounds() : elem.bounds; + final bounds = recalculateChilds || elem.bounds == null + ? elem.calculateBounds() + : elem.bounds; if (bounds != null) { builder.addBounds(bounds); } diff --git a/dart/geobase/lib/src/vector_data/model/bounded/bounded.dart b/dart/geobase/lib/src/vector_data/model/bounded/bounded.dart index 41f5c0e9..a712d91b 100644 --- a/dart/geobase/lib/src/vector_data/model/bounded/bounded.dart +++ b/dart/geobase/lib/src/vector_data/model/bounded/bounded.dart @@ -60,9 +60,8 @@ abstract class Bounded { /// /// The returned subtype must be the same as the type of this. /// - /// Note that any available [bounds] object on this is not projected (that is - /// the bounds for a returned object is null). + /// If [bounds] object is available on this, it's recalculated after + /// projecting geometries. If [bounds] is null, then it's null after + /// projecting too. Bounded project(Projection projection); - - // NOTE: add an optional param to "project" to ask calcuting bounds after op } diff --git a/dart/geobase/lib/src/vector_data/model/feature/feature.dart b/dart/geobase/lib/src/vector_data/model/feature/feature.dart index 4df661d3..7c51b06d 100644 --- a/dart/geobase/lib/src/vector_data/model/feature/feature.dart +++ b/dart/geobase/lib/src/vector_data/model/feature/feature.dart @@ -239,7 +239,7 @@ class Feature extends FeatureObject { item: geometry, // the main geometry of Feature collection: customGeometries?.values, // other geoms of CustomFeature type: resolveCoordType(), - calculateChilds: true, + recalculateChilds: true, ); @override @@ -259,18 +259,31 @@ class Feature extends FeatureObject { ? BoundsBuilder.calculateBounds( item: geom, type: resolveCoordTypeFrom(item: geom), - calculateChilds: false, + recalculateChilds: false, ) : bounds, ); } @override - Feature project(Projection projection) => Feature( - id: _id, - geometry: _geometry?.project(projection) as T?, - properties: _properties, - ); + Feature project(Projection projection) { + final projectedGeom = _geometry?.project(projection) as T?; + + return Feature( + id: _id, + geometry: projectedGeom, + properties: _properties, + + // bounds calculated from projected geometry if there was bounds before + bounds: bounds != null && projectedGeom != null + ? BoundsBuilder.calculateBounds( + item: projectedGeom, + type: resolveCoordTypeFrom(item: projectedGeom), + recalculateChilds: false, + ) + : null, + ); + } @override void writeTo(FeatureContent writer) { @@ -514,22 +527,41 @@ class _CustomFeature extends Feature { item: geom, collection: custGeom?.values, ), - calculateChilds: false, + recalculateChilds: false, ) : bounds, ); } @override - Feature project(Projection projection) => _CustomFeature( - id: _id, - geometry: _geometry?.project(projection) as T?, - properties: _properties, - custom: _custom, - customGeometries: _customGeometries?.map( - (key, geom) => MapEntry(key, geom.project(projection)), - ), - ); + Feature project(Projection projection) { + final projectedGeom = _geometry?.project(projection) as T?; + final projectedCustGeom = _customGeometries?.map( + (key, geom) => MapEntry(key, geom.project(projection)), + ); + + return _CustomFeature( + id: _id, + geometry: projectedGeom, + properties: _properties, + custom: _custom, + customGeometries: projectedCustGeom, + + // bounds calculated from projected geometries if there was bounds before + bounds: + bounds != null && (projectedGeom != null || projectedCustGeom != null) + ? BoundsBuilder.calculateBounds( + item: projectedGeom, + collection: projectedCustGeom?.values, + type: resolveCoordTypeFrom( + item: projectedGeom, + collection: projectedCustGeom?.values, + ), + recalculateChilds: false, + ) + : null, + ); + } @override void writeTo(FeatureContent writer) { diff --git a/dart/geobase/lib/src/vector_data/model/feature/feature_collection.dart b/dart/geobase/lib/src/vector_data/model/feature/feature_collection.dart index 1c6980ee..220c2dea 100644 --- a/dart/geobase/lib/src/vector_data/model/feature/feature_collection.dart +++ b/dart/geobase/lib/src/vector_data/model/feature/feature_collection.dart @@ -168,7 +168,7 @@ class FeatureCollection extends FeatureObject { BoxCoords? calculateBounds() => BoundsBuilder.calculateBounds( collection: features, type: resolveCoordType(), - calculateChilds: true, + recalculateChilds: true, ); @override @@ -189,19 +189,32 @@ class FeatureCollection extends FeatureObject { ? BoundsBuilder.calculateBounds( collection: collection, type: resolveCoordTypeFrom(collection: collection), - calculateChilds: false, + recalculateChilds: false, ) : bounds, ); } @override - FeatureCollection project(Projection projection) => FeatureCollection._( - _features - .map((feature) => feature.project(projection) as E) - .toList(growable: false), - _custom, - ); + FeatureCollection project(Projection projection) { + final projected = _features + .map((feature) => feature.project(projection) as E) + .toList(growable: false); + + return FeatureCollection._( + projected, + _custom, + + // bounds calculated from projected collection if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + collection: projected, + type: resolveCoordTypeFrom(collection: projected), + recalculateChilds: false, + ) + : null, + ); + } @override void writeTo(FeatureContent writer) { diff --git a/dart/geobase/lib/src/vector_data/model/feature/feature_object.dart b/dart/geobase/lib/src/vector_data/model/feature/feature_object.dart index f563fef1..dd9cae1c 100644 --- a/dart/geobase/lib/src/vector_data/model/feature/feature_object.dart +++ b/dart/geobase/lib/src/vector_data/model/feature/feature_object.dart @@ -33,8 +33,9 @@ abstract class FeatureObject extends Bounded { /// Any custom data or properties (other than geometries) are not projected, /// just copied (by references). /// - /// Note that any available [bounds] object on this is not projected (that is - /// the bounds for a returned feature object is null). + /// If [bounds] object is available on this, it's recalculated after + /// projecting geometries. If [bounds] is null, then it's null after + /// projecting too. @override FeatureObject project(Projection projection); diff --git a/dart/geobase/lib/src/vector_data/model/geometry/geometry.dart b/dart/geobase/lib/src/vector_data/model/geometry/geometry.dart index 38212667..6834da5b 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/geometry.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/geometry.dart @@ -48,8 +48,9 @@ abstract class Geometry extends Bounded { /// /// The returned geometry sub type must be the same as the type of this. /// - /// Note that any available [bounds] object on this is not projected (that is - /// the bounds for a returned geometry is null). + /// If [bounds] object is available on this, it's recalculated after + /// projecting geometries. If [bounds] is null, then it's null after + /// projecting too. @override Geometry project(Projection projection); diff --git a/dart/geobase/lib/src/vector_data/model/geometry/geometry_collection.dart b/dart/geobase/lib/src/vector_data/model/geometry/geometry_collection.dart index c92bb2b0..3b2a44d6 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/geometry_collection.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/geometry_collection.dart @@ -140,7 +140,7 @@ class GeometryCollection extends Geometry { BoxCoords? calculateBounds() => BoundsBuilder.calculateBounds( collection: _geometries, type: resolveCoordType(), - calculateChilds: true, + recalculateChilds: true, ); @override @@ -161,18 +161,31 @@ class GeometryCollection extends Geometry { ? BoundsBuilder.calculateBounds( collection: collection, type: resolveCoordTypeFrom(collection: collection), - calculateChilds: false, + recalculateChilds: false, ) : bounds, ); } @override - GeometryCollection project(Projection projection) => GeometryCollection( - _geometries - .map((geometry) => geometry.project(projection) as E) - .toList(growable: false), - ); + GeometryCollection project(Projection projection) { + final projected = _geometries + .map((geometry) => geometry.project(projection) as E) + .toList(growable: false); + + return GeometryCollection( + projected, + + // bounds calculated from projected collection if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + collection: projected, + type: resolveCoordTypeFrom(collection: projected), + recalculateChilds: false, + ) + : null, + ); + } @override void writeTo(GeometryContent writer, {String? name}) => isEmpty diff --git a/dart/geobase/lib/src/vector_data/model/geometry/linestring.dart b/dart/geobase/lib/src/vector_data/model/geometry/linestring.dart index 63af0bcf..122a7ebc 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/linestring.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/linestring.dart @@ -190,8 +190,21 @@ class LineString extends SimpleGeometry { } @override - LineString project(Projection projection) => - LineString(_chain.project(projection)); + LineString project(Projection projection) { + final projected = _chain.project(projection); + + return LineString( + projected, + + // bounds calculated from projected chain if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + array: projected, + type: coordType, + ) + : null, + ); + } @override void writeTo(SimpleGeometryContent writer, {String? name}) => isEmpty diff --git a/dart/geobase/lib/src/vector_data/model/geometry/multi_linestring.dart b/dart/geobase/lib/src/vector_data/model/geometry/multi_linestring.dart index c370d65b..02634241 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/multi_linestring.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/multi_linestring.dart @@ -223,12 +223,24 @@ class MultiLineString extends SimpleGeometry { } @override - MultiLineString project(Projection projection) => MultiLineString._( - _lineStrings - .map((chain) => chain.project(projection)) - .toList(growable: false), - type: _type, - ); + MultiLineString project(Projection projection) { + final projected = _lineStrings + .map((chain) => chain.project(projection)) + .toList(growable: false); + + return MultiLineString._( + projected, + type: _type, + + // bounds calculated from projected geometry if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + arrays: projected, + type: coordType, + ) + : null, + ); + } @override void writeTo(SimpleGeometryContent writer, {String? name}) => isEmpty diff --git a/dart/geobase/lib/src/vector_data/model/geometry/multi_point.dart b/dart/geobase/lib/src/vector_data/model/geometry/multi_point.dart index 039b04d5..420e65dd 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/multi_point.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/multi_point.dart @@ -199,12 +199,24 @@ class MultiPoint extends SimpleGeometry { } @override - MultiPoint project(Projection projection) => MultiPoint._( - _points - .map((pos) => projection.project(pos, to: PositionCoords.create)) - .toList(growable: false), - type: _type, - ); + MultiPoint project(Projection projection) { + final projected = _points + .map((pos) => projection.project(pos, to: PositionCoords.create)) + .toList(growable: false); + + return MultiPoint._( + projected, + type: _type, + + // bounds calculated from projected geometry if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + positions: projected, + type: coordType, + ) + : null, + ); + } @override void writeTo(SimpleGeometryContent writer, {String? name}) => isEmpty diff --git a/dart/geobase/lib/src/vector_data/model/geometry/multi_polygon.dart b/dart/geobase/lib/src/vector_data/model/geometry/multi_polygon.dart index 8cd99a3d..141bf125 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/multi_polygon.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/multi_polygon.dart @@ -221,9 +221,11 @@ class MultiPolygon extends SimpleGeometry { /// All polygons as a lazy iterable of [Polygon] geometries. Iterable get polygons => ringArrays.map(Polygon.new); - Iterable get _allRings { + static Iterable _allRings( + List> ringArrays, + ) { Iterable? iter; - for (final rings in _polygons) { + for (final rings in ringArrays) { iter = iter == null ? rings : iter.followedBy(rings); } return iter ?? []; @@ -231,7 +233,7 @@ class MultiPolygon extends SimpleGeometry { @override BoxCoords? calculateBounds() => BoundsBuilder.calculateBounds( - arrays: _allRings, + arrays: _allRings(_polygons), type: coordType, ); @@ -245,7 +247,7 @@ class MultiPolygon extends SimpleGeometry { _polygons, type: _type, bounds: BoundsBuilder.calculateBounds( - arrays: _allRings, + arrays: _allRings(_polygons), type: coordType, ), ); @@ -256,16 +258,28 @@ class MultiPolygon extends SimpleGeometry { } @override - MultiPolygon project(Projection projection) => MultiPolygon._( - _polygons - .map>( - (rings) => rings - .map((ring) => ring.project(projection)) - .toList(growable: false), + MultiPolygon project(Projection projection) { + final projected = _polygons + .map>( + (rings) => rings + .map((ring) => ring.project(projection)) + .toList(growable: false), + ) + .toList(growable: false); + + return MultiPolygon._( + projected, + type: _type, + + // bounds calculated from projected geometry if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + arrays: _allRings(projected), + type: coordType, ) - .toList(growable: false), - type: _type, - ); + : null, + ); + } @override void writeTo(SimpleGeometryContent writer, {String? name}) => isEmpty diff --git a/dart/geobase/lib/src/vector_data/model/geometry/polygon.dart b/dart/geobase/lib/src/vector_data/model/geometry/polygon.dart index 4364ac9b..ac20f75a 100644 --- a/dart/geobase/lib/src/vector_data/model/geometry/polygon.dart +++ b/dart/geobase/lib/src/vector_data/model/geometry/polygon.dart @@ -255,10 +255,23 @@ class Polygon extends SimpleGeometry { } @override - Polygon project(Projection projection) => Polygon._( - _rings.map((ring) => ring.project(projection)).toList(growable: false), - type: _type, - ); + Polygon project(Projection projection) { + final projected = + _rings.map((ring) => ring.project(projection)).toList(growable: false); + + return Polygon._( + projected, + type: _type, + + // bounds calculated from projected geometry if there was bounds before + bounds: bounds != null + ? BoundsBuilder.calculateBounds( + arrays: projected, + type: coordType, + ) + : null, + ); + } @override void writeTo(SimpleGeometryContent writer, {String? name}) => isEmpty diff --git a/dart/geobase/test/projections/projection_sample.dart b/dart/geobase/test/projections/projection_sample.dart index 23db2a07..6544c5ae 100644 --- a/dart/geobase/test/projections/projection_sample.dart +++ b/dart/geobase/test/projections/projection_sample.dart @@ -4,7 +4,7 @@ // // Docs: https://github.com/navibyte/geospatial -// ignore_for_file: avoid_print +// ignore_for_file: avoid_print, lines_longer_than_80_chars import 'package:geobase/geobase.dart'; @@ -60,6 +60,21 @@ const wgs84ToWebMercatorData = [ [179.9999999, 89.9, 20037508.33, 44927335.42709685], ]; +const wgs84ToWebMercatorDataBounds = [ + [ + '-180.0,-89.9,179.9999999,89.9', + '-20037508.34,-44927335.43,20037508.33,44927335.43', + ], + [ + '-180.0,-89.9,10.0,179.9999999,89.9,10.0', + '-20037508.34,-44927335.43,10,20037508.33,44927335.43,10', + ], + [ + '-180.0,-89.9,10.0,10.0,179.9999999,89.9,10.0,10.0', + '-20037508.34,-44927335.43,10,10,20037508.33,44927335.43,10,10' + ], +]; + Iterable testWgs84Points() => wgs84ToWebMercatorData .map((coords) => Geographic(lon: coords[0], lat: coords[1])) .toList(growable: false); diff --git a/dart/geobase/test/vector_data/vector_data_projections_test.dart b/dart/geobase/test/vector_data/vector_data_projections_test.dart index da643677..f5c61bc1 100644 --- a/dart/geobase/test/vector_data/vector_data_projections_test.dart +++ b/dart/geobase/test/vector_data/vector_data_projections_test.dart @@ -84,6 +84,7 @@ void main() { source, 0.01, ); + final sourceLineString = LineString(sourceArray); final targetLineString = LineString(targetArray); expectCoords( @@ -96,6 +97,23 @@ void main() { source, 0.01, ); + + expect( + sourceLineString.bounded().bounds?.toText(), + wgs84ToWebMercatorDataBounds[dim - 2][0], + ); + expect( + targetLineString.bounded().bounds?.toText(decimals: 2), + wgs84ToWebMercatorDataBounds[dim - 2][1], + ); + expect( + sourceLineString + .bounded() + .project(forward) + .bounds + ?.toText(decimals: 2), + wgs84ToWebMercatorDataBounds[dim - 2][1], + ); } }); });