Skip to content

Commit

Permalink
refactor(geobase): simplified code behind equalsCoords, equals2D, equ…
Browse files Browse the repository at this point in the history
…als3D of geometries and features #188
  • Loading branch information
navispatial committed Aug 31, 2023
1 parent ce52384 commit f8a4a20
Show file tree
Hide file tree
Showing 10 changed files with 536 additions and 624 deletions.
104 changes: 104 additions & 0 deletions dart/geobase/lib/src/utils/bounded_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2020-2023 Navibyte (https://navibyte.com). All rights reserved.
// Use of this source code is governed by a “BSD-3-Clause”-style license that is
// specified in the LICENSE file.
//
// Docs: https://github.com/navibyte/geospatial

import 'package:meta/meta.dart';

import '/src/constants/epsilon.dart';
import '/src/vector_data/model/bounded/bounded.dart';

import 'tolerance.dart';

/// True if [b1] and [b2] contain exactly same coordinate values
/// (or both are empty) in the same order and with the same coordinate type.
///
/// The [test] function should test only coordinate data.
///
/// This static method helps implementing [Bounded.equalsCoords] in subtypes.
@internal
bool testEqualsCoords<T extends Bounded>(
Bounded b1,
Bounded b2,
bool Function(T, T) test,
) {
if (b1 is! T || b2 is! T) return false;
if (identical(b1, b2)) return true;

// test bounding boxes if both bounded objects have it
final bb1 = b1.bounds;
final bb2 = b2.bounds;
if (bb1 != null && bb2 != null && bb1 != bb2) {
// both bounded objects has bounding boxes and boxes do not equal
return false;
}

return test.call(b1, b2);
}

/// True if [b1] equals with [b2] by testing 2D coordinates
/// of all geometries (that must be in same order in both objects) contained
/// directly or by child objects.
///
/// This static method helps implementing [Bounded.equals2D] in subtypes.
@internal
bool testEquals2D<T extends Bounded>(
Bounded b1,
Bounded b2,
bool Function(T, T) test, {
double toleranceHoriz = defaultEpsilon,
}) {
assertTolerance(toleranceHoriz);
if (b1 is! T || b2 is! T) return false;
if (b1.isEmptyByGeometry || b2.isEmptyByGeometry) return false;

// test bounding boxes if both bounded objects have it
final bb1 = b1.bounds;
final bb2 = b2.bounds;
if (bb1 != null &&
bb2 != null &&
!bb1.equals2D(
bb2,
toleranceHoriz: toleranceHoriz,
)) {
// both bounded objects has bounding boxes and boxes do not equal in 2D
return false;
}
return test.call(b1, b2);
}

/// True if [b1] equals with [b2] by testing 3D coordinates
/// of all geometries (that must be in same order in both objects) contained
/// directly or by child objects.
///
/// This static method helps implementing [Bounded.equals3D] in subtypes.
@internal
bool testEquals3D<T extends Bounded>(
Bounded b1,
Bounded b2,
bool Function(T, T) test, {
double toleranceHoriz = defaultEpsilon,
double toleranceVert = defaultEpsilon,
}) {
assertTolerance(toleranceHoriz);
assertTolerance(toleranceVert);
if (b1 is! T || b2 is! T) return false;
if (b1.isEmptyByGeometry || b2.isEmptyByGeometry) return false;
if (!b1.coordType.is3D || !b2.coordType.is3D) return false;

// test bounding boxes if both bounded objects have it
final bb1 = b1.bounds;
final bb2 = b2.bounds;
if (bb1 != null &&
bb2 != null &&
!bb1.equals3D(
bb2,
toleranceHoriz: toleranceHoriz,
toleranceVert: toleranceVert,
)) {
// both bounded objects has bounding boxes and boxes do not equal in 3D
return false;
}
return test.call(b1, b2);
}
149 changes: 58 additions & 91 deletions dart/geobase/lib/src/vector_data/model/feature/feature.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import '/src/constants/epsilon.dart';
import '/src/coordinates/base/box.dart';
import '/src/coordinates/projection/projection.dart';
import '/src/coordinates/reference/coord_ref_sys.dart';
import '/src/utils/bounded_utils.dart';
import '/src/utils/bounds_builder.dart';
import '/src/utils/coord_type.dart';
import '/src/utils/tolerance.dart';
import '/src/vector/content/feature_content.dart';
import '/src/vector/content/geometry_content.dart';
import '/src/vector/encoding/text_format.dart';
Expand Down Expand Up @@ -346,107 +346,56 @@ class Feature<T extends Geometry> extends FeatureObject {
}

@override
bool equalsCoords(Bounded other) {
if (other is! Feature) return false;
if (identical(this, other)) return true;

if (bounds != null && other.bounds != null && !(bounds! == other.bounds!)) {
// both feature collections has bound boxes and boxes do not equal
return false;
}

// test main geometry
final mg1 = geometry;
final mg2 = other.geometry;
if (mg1 != null) {
if (mg2 == null) return false;
if (!mg1.equalsCoords(mg2)) return false;
} else {
if (mg2 != null) return false;
}

return true;
}
bool equalsCoords(Bounded other) => testEqualsCoords<Feature<T>>(
this,
other,
(feature1, feature2) => _testFeatures<T>(
feature1,
feature2,
(geometry1, geometry2) => geometry1.equalsCoords(geometry2),
),
);

@override
bool equals2D(
Bounded other, {
double toleranceHoriz = defaultEpsilon,
}) {
assertTolerance(toleranceHoriz);
if (other is! Feature) return false;
if (isEmptyByGeometry || other.isEmptyByGeometry) return false;

// test bounding boxes if both have it
if (bounds != null &&
other.bounds != null &&
!bounds!.equals2D(
other.bounds!,
toleranceHoriz: toleranceHoriz,
)) {
// both features has bound boxes and boxes do not equal in 2D
return false;
}

// test main geometry
final mg1 = geometry;
final mg2 = other.geometry;
if (mg1 == null ||
mg2 == null ||
mg1.isEmptyByGeometry ||
mg2.isEmptyByGeometry) return false;
if (!mg1.equals2D(
mg2,
toleranceHoriz: toleranceHoriz,
)) {
return false;
}

// got here, features equals in 2D
return true;
}
}) =>
testEquals2D<Feature<T>>(
this,
other,
(feature1, feature2) => _testFeatures<T>(
feature1,
feature2,
(geometry1, geometry2) => geometry1.equals2D(
geometry2,
toleranceHoriz: toleranceHoriz,
),
),
toleranceHoriz: toleranceHoriz,
);

@override
bool equals3D(
Bounded other, {
double toleranceHoriz = defaultEpsilon,
double toleranceVert = defaultEpsilon,
}) {
assertTolerance(toleranceHoriz);
assertTolerance(toleranceVert);
if (other is! Feature) return false;
if (isEmptyByGeometry || other.isEmptyByGeometry) return false;

// test bounding boxes if both have it
if (bounds != null &&
other.bounds != null &&
!bounds!.equals3D(
other.bounds!,
toleranceHoriz: toleranceHoriz,
toleranceVert: toleranceVert,
)) {
// both features has bound boxes and boxes do not equal in 3D
return false;
}

// test main geometry
final mg1 = geometry;
final mg2 = other.geometry;
if (mg1 == null ||
mg2 == null ||
mg1.isEmptyByGeometry ||
mg2.isEmptyByGeometry) return false;
if (!mg1.equals3D(
mg2,
toleranceHoriz: toleranceHoriz,
toleranceVert: toleranceVert,
)) {
return false;
}

// got here, features equals in 3D
return true;
}
}) =>
testEquals3D<Feature<T>>(
this,
other,
(feature1, feature2) => _testFeatures<T>(
feature1,
feature2,
(geometry1, geometry2) => geometry1.equals3D(
geometry2,
toleranceHoriz: toleranceHoriz,
toleranceVert: toleranceVert,
),
),
toleranceHoriz: toleranceHoriz,
toleranceVert: toleranceVert,
);

@override
bool operator ==(Object other) =>
Expand All @@ -473,3 +422,21 @@ Box? _buildBoundsFrom(Geometry geometry) => BoundsBuilder.calculateBounds(
type: resolveCoordTypeFrom(item: geometry),
recalculateChilds: false,
);

bool _testFeatures<T extends Geometry>(
Feature<T> feature1,
Feature<T> feature2,
bool Function(T, T) testGeometries,
) {
// test geometries contained
final geom1 = feature1.geometry;
final geom2 = feature2.geometry;

if (geom1 != null) {
if (geom2 == null) return false;
if (!testGeometries(geom1, geom2)) return false;
} else {
if (geom2 != null) return false;
}
return true;
}
Loading

0 comments on commit f8a4a20

Please sign in to comment.