diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd76f8d..2cdee536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +- Add `PreparedGeometry` type and `Prepare` function for efficient repeated + spatial predicate evaluation. When a geometry is prepared, it caches spatial + indices so that subsequent calls to `Intersects`, `Contains`, + `ContainsProperly`, `CoveredBy`, `Covers`, `Crosses`, `Disjoint`, `Overlaps`, + `Touches`, and `Within` against different test geometries are fast. The + implementation is based on a port of JTS. + - Add `Buffer` function that computes the buffer of a geometry at a given distance. Options are available for controlling quad segments, end cap style (round, flat, square), join style (round, mitre, bevel), single-sided mode, diff --git a/geom/alg_prepared.go b/geom/alg_prepared.go new file mode 100644 index 00000000..972b9ceb --- /dev/null +++ b/geom/alg_prepared.go @@ -0,0 +1,96 @@ +package geom + +import "github.com/peterstace/simplefeatures/internal/jtsport/jts" + +// PreparedGeometry is a geometry that has been preprocessed to efficiently +// enable evaluation of spatial predicates against many other geometries. +// +// It is created by calling [Prepare] with the geometry to be prepared. The +// prepared geometry caches spatial indices and other structures so that +// repeated predicate evaluations (e.g. [PreparedGeometry.Intersects], +// [PreparedGeometry.Contains]) against different test geometries are fast. +type PreparedGeometry struct { + prep jts.GeomPrep_PreparedGeometry +} + +// Prepare preprocesses a geometry for efficient repeated predicate evaluation. +func Prepare(g Geometry) (PreparedGeometry, error) { + var pg PreparedGeometry + err := catch(func() error { + jtsG, err := toJTS(g) + if err != nil { + return err + } + pg.prep = jts.GeomPrep_PreparedGeometryFactory_Prepare(jtsG) + return nil + }) + return pg, err +} + +func toJTS(g Geometry) (*jts.Geom_Geometry, error) { + jtsG, err := jts.Io_NewWKBReader().ReadBytes(g.AsBinary()) + if err != nil { + return nil, wrap(err, "converting geometry to JTS") + } + return jtsG, nil +} + +func (p PreparedGeometry) eval(g Geometry, pred func(*jts.Geom_Geometry) bool) (bool, error) { + var result bool + err := catch(func() error { + jtsG, err := toJTS(g) + if err != nil { + return err + } + result = pred(jtsG) + return nil + }) + return result, err +} + +// Intersects reports whether the prepared geometry intersects g. +func (p PreparedGeometry) Intersects(g Geometry) (bool, error) { + return p.eval(g, p.prep.Intersects) +} + +// Contains reports whether the prepared geometry contains g. +func (p PreparedGeometry) Contains(g Geometry) (bool, error) { + return p.eval(g, p.prep.Contains) +} + +// ContainsProperly reports whether the prepared geometry properly contains g. +// A geometry properly contains another if it contains it and the other +// geometry has no points on the boundary of the prepared geometry. +func (p PreparedGeometry) ContainsProperly(g Geometry) (bool, error) { + return p.eval(g, p.prep.ContainsProperly) +} + +// CoveredBy reports whether the prepared geometry is covered by g. +func (p PreparedGeometry) CoveredBy(g Geometry) (bool, error) { + return p.eval(g, p.prep.CoveredBy) +} + +// Covers reports whether the prepared geometry covers g. +func (p PreparedGeometry) Covers(g Geometry) (bool, error) { + return p.eval(g, p.prep.Covers) +} + +// Disjoint reports whether the prepared geometry is disjoint from g. +func (p PreparedGeometry) Disjoint(g Geometry) (bool, error) { + return p.eval(g, p.prep.Disjoint) +} + +// Overlaps reports whether the prepared geometry overlaps g. +func (p PreparedGeometry) Overlaps(g Geometry) (bool, error) { + return p.eval(g, p.prep.Overlaps) +} + +// Touches reports whether the prepared geometry touches g. +func (p PreparedGeometry) Touches(g Geometry) (bool, error) { + return p.eval(g, p.prep.Touches) +} + +// Within reports whether the prepared geometry is within g. +func (p PreparedGeometry) Within(g Geometry) (bool, error) { + return p.eval(g, p.prep.Within) +} diff --git a/geom/alg_prepared_test.go b/geom/alg_prepared_test.go new file mode 100644 index 00000000..dec0588d --- /dev/null +++ b/geom/alg_prepared_test.go @@ -0,0 +1,249 @@ +package geom_test + +import ( + "strconv" + "testing" + + "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" +) + +func TestPreparedGeometry(t *testing.T) { + for i, tt := range []struct { + wkt1, wkt2 string + }{ + // Point vs point + {"POINT(1 2)", "POINT(1 2)"}, + {"POINT(1 2)", "POINT(3 4)"}, + + // Point vs linestring + {"POINT(0.5 0)", "LINESTRING(0 0,1 0)"}, + {"POINT(0 0)", "LINESTRING(0 0,1 0)"}, + {"POINT(0 1)", "LINESTRING(0 0,1 0)"}, + + // Point vs polygon + {"POINT(0.5 0.5)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + {"POINT(0 0)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + {"POINT(5 5)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + + // Linestring vs linestring + {"LINESTRING(0 0,1 1)", "LINESTRING(0 1,1 0)"}, + {"LINESTRING(0 0,1 0)", "LINESTRING(1 0,2 0)"}, + {"LINESTRING(0 0,2 0)", "LINESTRING(1 0,3 0)"}, + {"LINESTRING(0 0,1 0)", "LINESTRING(2 0,3 0)"}, + + // Linestring vs polygon + {"LINESTRING(0 0,2 2)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + {"LINESTRING(0.25 0.25,0.75 0.75)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + {"LINESTRING(5 5,6 6)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + {"LINESTRING(0 0,1 0)", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + + // Polygon vs polygon + {"POLYGON((0 0,3 0,3 3,0 3,0 0))", "POLYGON((1 1,2 1,2 2,1 2,1 1))"}, + {"POLYGON((1 1,2 1,2 2,1 2,1 1))", "POLYGON((0 0,3 0,3 3,0 3,0 0))"}, + {"POLYGON((0 0,2 0,2 2,0 2,0 0))", "POLYGON((1 0,3 0,3 2,1 2,1 0))"}, + {"POLYGON((0 0,1 0,1 1,0 1,0 0))", "POLYGON((1 0,2 0,2 1,1 1,1 0))"}, + {"POLYGON((0 0,1 0,1 1,0 1,0 0))", "POLYGON((2 2,3 2,3 3,2 3,2 2))"}, + {"POLYGON((0 0,1 0,1 1,0 1,0 0))", "POLYGON((0 0,1 0,1 1,0 1,0 0))"}, + + // Empty geometries + {"POINT EMPTY", "POINT EMPTY"}, + {"POINT EMPTY", "POINT(1 2)"}, + {"POLYGON((0 0,1 0,1 1,0 1,0 0))", "POINT EMPTY"}, + + // Polygon with hole + {"POLYGON((0 0,10 0,10 10,0 10,0 0),(3 3,7 3,7 7,3 7,3 3))", "POINT(5 5)"}, + {"POLYGON((0 0,10 0,10 10,0 10,0 0),(3 3,7 3,7 7,3 7,3 3))", "POINT(1 1)"}, + + // Multi-geometries + {"MULTIPOINT((0 0),(1 1))", "POINT(0 0)"}, + {"MULTILINESTRING((0 0,1 0),(0 1,1 1))", "POINT(0.5 0)"}, + {"MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)),((2 2,3 2,3 3,2 3,2 2)))", "POINT(0.5 0.5)"}, + + // Geometry collection as prepared geometry + {"GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(1 1,2 2),POLYGON((3 3,6 3,6 6,3 6,3 3)))", "POINT(0 0)"}, + {"GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(1 1,2 2),POLYGON((3 3,6 3,6 6,3 6,3 3)))", "POINT(4 4)"}, + {"GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(1 1,2 2),POLYGON((3 3,6 3,6 6,3 6,3 3)))", "POINT(9 9)"}, + {"GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(1 1,2 2),POLYGON((3 3,6 3,6 6,3 6,3 3)))", "LINESTRING(4 4,5 5)"}, + + // Geometry collection as test geometry + {"POLYGON((0 0,10 0,10 10,0 10,0 0))", "GEOMETRYCOLLECTION(POINT(1 1),LINESTRING(2 2,3 3))"}, + {"POLYGON((0 0,10 0,10 10,0 10,0 0))", "GEOMETRYCOLLECTION(POINT(1 1),POINT(20 20))"}, + + // Both geometry collections + {"GEOMETRYCOLLECTION(POINT(0 0),POLYGON((1 1,4 1,4 4,1 4,1 1)))", "GEOMETRYCOLLECTION(POINT(2 2),LINESTRING(2 2,3 3))"}, + {"GEOMETRYCOLLECTION(POINT(0 0),POLYGON((1 1,4 1,4 4,1 4,1 1)))", "GEOMETRYCOLLECTION(POINT(20 20),LINESTRING(20 20,30 30))"}, + + // Empty geometry collection + {"GEOMETRYCOLLECTION EMPTY", "POINT(1 1)"}, + {"POINT(1 1)", "GEOMETRYCOLLECTION EMPTY"}, + {"GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"}, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) + + pg, err := geom.Prepare(g1) + test.NoErr(t, err) + + type predicate struct { + name string + got func() (bool, error) + want func() (bool, error) + } + + predicates := []predicate{ + { + name: "Intersects", + got: func() (bool, error) { return pg.Intersects(g2) }, + want: func() (bool, error) { return geom.Intersects(g1, g2), nil }, + }, + { + name: "Contains", + got: func() (bool, error) { return pg.Contains(g2) }, + want: func() (bool, error) { return geom.Contains(g1, g2) }, + }, + { + name: "CoveredBy", + got: func() (bool, error) { return pg.CoveredBy(g2) }, + want: func() (bool, error) { return geom.CoveredBy(g1, g2) }, + }, + { + name: "Covers", + got: func() (bool, error) { return pg.Covers(g2) }, + want: func() (bool, error) { return geom.Covers(g1, g2) }, + }, + { + name: "Disjoint", + got: func() (bool, error) { return pg.Disjoint(g2) }, + want: func() (bool, error) { return geom.Disjoint(g1, g2) }, + }, + { + name: "Overlaps", + got: func() (bool, error) { return pg.Overlaps(g2) }, + want: func() (bool, error) { return geom.Overlaps(g1, g2) }, + }, + { + name: "Touches", + got: func() (bool, error) { return pg.Touches(g2) }, + want: func() (bool, error) { return geom.Touches(g1, g2) }, + }, + { + name: "Within", + got: func() (bool, error) { return pg.Within(g2) }, + want: func() (bool, error) { return geom.Within(g1, g2) }, + }, + } + + for _, pred := range predicates { + t.Run(pred.name, func(t *testing.T) { + got, gotErr := pred.got() + test.NoErr(t, gotErr) + want, wantErr := pred.want() + test.NoErr(t, wantErr) + test.Eq(t, got, want) + }) + } + }) + } +} + +func TestPreparedGeometryContainsProperly(t *testing.T) { + for i, tt := range []struct { + wkt1, wkt2 string + want bool + }{ + // Point in polygon interior: true + { + wkt1: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + wkt2: "POINT(0.5 0.5)", + want: true, + }, + // Point on polygon boundary: false (key distinction from Contains) + { + wkt1: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + wkt2: "POINT(0 0)", + want: false, + }, + // Point on polygon edge: false + { + wkt1: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + wkt2: "POINT(0.5 0)", + want: false, + }, + // Polygon properly containing another polygon + { + wkt1: "POLYGON((0 0,10 0,10 10,0 10,0 0))", + wkt2: "POLYGON((1 1,2 1,2 2,1 2,1 1))", + want: true, + }, + // Equal polygons: false (boundary points shared) + { + wkt1: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + wkt2: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + want: false, + }, + // Both empty + { + wkt1: "POINT EMPTY", + wkt2: "POINT EMPTY", + want: false, + }, + // One empty + { + wkt1: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + wkt2: "POINT EMPTY", + want: false, + }, + // Disjoint + { + wkt1: "POLYGON((0 0,1 0,1 1,0 1,0 0))", + wkt2: "POINT(5 5)", + want: false, + }, + // Polygon with shared boundary edge: false + { + wkt1: "POLYGON((0 0,2 0,2 2,0 2,0 0))", + wkt2: "POLYGON((1 0,2 0,2 1,1 1,1 0))", + want: false, + }, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) + + pg, err := geom.Prepare(g1) + test.NoErr(t, err) + + got, err := pg.ContainsProperly(g2) + test.NoErr(t, err) + test.Eq(t, got, tt.want) + }) + } +} + +func TestPreparedGeometryMultipleEvaluations(t *testing.T) { + pg, err := geom.Prepare(test.FromWKT(t, "POLYGON((0 0,10 0,10 10,0 10,0 0))")) + test.NoErr(t, err) + + tests := []struct { + wkt string + want bool + }{ + {"POINT(5 5)", true}, + {"POINT(15 15)", false}, + {"LINESTRING(1 1,2 2)", true}, + {"LINESTRING(11 11,12 12)", false}, + {"POLYGON((1 1,2 1,2 2,1 2,1 1))", true}, + {"POLYGON((20 20,21 20,21 21,20 21,20 20))", false}, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + g := test.FromWKT(t, tt.wkt) + got, err := pg.Intersects(g) + test.NoErr(t, err) + test.Eq(t, got, tt.want) + }) + } +} diff --git a/internal/jtsport/MANIFEST.csv b/internal/jtsport/MANIFEST.csv index 9f912229..84388105 100644 --- a/internal/jtsport/MANIFEST.csv +++ b/internal/jtsport/MANIFEST.csv @@ -7,12 +7,6 @@ algorithm/BoundaryNodeRule.java,algorithm_boundary_node_rule.go,ported algorithm/CGAlgorithmsDD.java,algorithm_cgalgorithms_dd.go,ported algorithm/CGAlgorithmsDDTest.java,algorithm_cgalgorithms_dd_test.go,ported algorithm/Distance.java,algorithm_distance.go,ported -algorithm/distance/DiscreteFrechetDistance.java,algorithm_distance_discrete_frechet_distance.go,ported -algorithm/distance/DiscreteFrechetDistanceTest.java,algorithm_distance_discrete_frechet_distance_test.go,ported -algorithm/distance/DiscreteHausdorffDistance.java,algorithm_distance_discrete_hausdorff_distance.go,ported -algorithm/distance/DiscreteHausdorffDistanceTest.java,algorithm_distance_discrete_hausdorff_distance_test.go,ported -algorithm/distance/DistanceToPoint.java,algorithm_distance_distance_to_point.go,ported -algorithm/distance/PointPairDistance.java,algorithm_distance_point_pair_distance.go,ported algorithm/DistanceTest.java,algorithm_distance_test.go,ported algorithm/HCoordinate.java,algorithm_hcoordinate.go,ported algorithm/Intersection.java,algorithm_intersection.go,ported @@ -20,11 +14,6 @@ algorithm/IntersectionTest.java,algorithm_intersection_test.go,ported algorithm/Length.java,algorithm_length.go,ported algorithm/LengthTest.java,algorithm_length_test.go,ported algorithm/LineIntersector.java,algorithm_line_intersector.go,ported -algorithm/locate/IndexedPointInAreaLocator.java,algorithm_locate_indexed_point_in_area_locator.go,ported -algorithm/locate/IndexedPointInAreaLocatorTest.java,algorithm_locate_indexed_point_in_area_locator_test.go,ported -algorithm/locate/PointOnGeometryLocator.java,algorithm_locate_point_on_geometry_locator.go,ported -algorithm/locate/SimplePointInAreaLocator.java,algorithm_locate_simple_point_in_area_locator.go,ported -algorithm/locate/SimplePointInAreaLocatorTest.java,algorithm_locate_simple_point_in_area_locator_test.go,ported algorithm/NotRepresentableException.java,algorithm_not_representable_exception.go,ported algorithm/Orientation.java,algorithm_orientation.go,ported algorithm/OrientationTest.java,algorithm_orientation_test.go,ported @@ -41,87 +30,58 @@ algorithm/RectangleLineIntersectorTest.java,algorithm_rectangle_line_intersector algorithm/RobustDeterminant.java,algorithm_robust_determinant.go,ported algorithm/RobustLineIntersector.java,algorithm_robust_line_intersector.go,ported algorithm/RobustLineIntersectorTest.java,algorithm_robust_line_intersector_test.go,ported +algorithm/distance/DiscreteFrechetDistance.java,algorithm_distance_discrete_frechet_distance.go,ported +algorithm/distance/DiscreteFrechetDistanceTest.java,algorithm_distance_discrete_frechet_distance_test.go,ported +algorithm/distance/DiscreteHausdorffDistance.java,algorithm_distance_discrete_hausdorff_distance.go,ported +algorithm/distance/DiscreteHausdorffDistanceTest.java,algorithm_distance_discrete_hausdorff_distance_test.go,ported +algorithm/distance/DistanceToPoint.java,algorithm_distance_distance_to_point.go,ported +algorithm/distance/PointPairDistance.java,algorithm_distance_point_pair_distance.go,ported +algorithm/locate/IndexedPointInAreaLocator.java,algorithm_locate_indexed_point_in_area_locator.go,ported +algorithm/locate/IndexedPointInAreaLocatorTest.java,algorithm_locate_indexed_point_in_area_locator_test.go,ported +algorithm/locate/PointOnGeometryLocator.java,algorithm_locate_point_on_geometry_locator.go,ported +algorithm/locate/SimplePointInAreaLocator.java,algorithm_locate_simple_point_in_area_locator.go,ported +algorithm/locate/SimplePointInAreaLocatorTest.java,algorithm_locate_simple_point_in_area_locator_test.go,ported edgegraph/HalfEdge.java,edgegraph_half_edge.go,ported +geom/Coordinate.java,geom_coordinate.go,ported geom/CoordinateArrays.java,geom_coordinate_arrays.go,ported geom/CoordinateArraysTest.java,geom_coordinate_arrays_test.go,ported geom/CoordinateFilter.java,geom_coordinate_filter.go,ported -geom/Coordinate.java,geom_coordinate.go,ported geom/CoordinateList.java,geom_coordinate_list.go,ported geom/CoordinateListTest.java,geom_coordinate_list_test.go,ported +geom/CoordinateSequence.java,geom_coordinate_sequence.go,ported geom/CoordinateSequenceComparator.java,geom_coordinate_sequence_comparator.go,ported geom/CoordinateSequenceFactory.java,geom_coordinate_sequence_factory.go,ported geom/CoordinateSequenceFilter.java,geom_coordinate_sequence_filter.go,ported -geom/CoordinateSequence.java,geom_coordinate_sequence.go,ported geom/CoordinateSequences.java,geom_coordinate_sequences.go,ported geom/CoordinateSequencesTest.java,geom_coordinate_sequences_test.go,ported -geom/Coordinates.java,geom_coordinates.go,ported geom/CoordinateTest.java,geom_coordinate_test.go,ported geom/CoordinateXY.java,geom_coordinate_xy.go,ported geom/CoordinateXYM.java,geom_coordinate_xym.go,ported geom/CoordinateXYZM.java,geom_coordinate_xyzm.go,ported +geom/Coordinates.java,geom_coordinates.go,ported geom/Dimension.java,geom_dimension.go,ported geom/Envelope.java,geom_envelope.go,ported geom/EnvelopeTest.java,geom_envelope_test.go,ported +geom/Geometry.java,geom_geometry.go,reviewed +geom/GeometryCollection.java,geom_geometry_collection.go,ported geom/GeometryCollectionIterator.java,geom_geometry_collection_iterator.go,ported geom/GeometryCollectionIteratorTest.java,geom_geometry_collection_iterator_test.go,ported -geom/GeometryCollection.java,geom_geometry_collection.go,ported geom/GeometryCollectionTest.java,geom_geometry_collection_test.go,ported geom/GeometryComponentFilter.java,geom_geometry_component_filter.go,ported geom/GeometryFactory.java,geom_geometry_factory.go,ported geom/GeometryFactoryTest.java,geom_geometry_factory_test.go,ported geom/GeometryFilter.java,geom_geometry_filter.go,ported -geom/Geometry.java,geom_geometry.go,reviewed geom/GeometryOverlay.java,geom_geometry_overlay.go,ported geom/GeometryOverlayTest.java,geom_geometry_overlay_test.go,ported geom/GeometryRelate.java,geom_geometry_relate.go,ported -geomop/GeometryMethodOperation.java,jtstest_geomop_geometry_method_operation.go,ported -geomop/GeometryOperation.java,jtstest_geomop_geometry_operation.go,ported -geomop/TestCaseGeometryFunctions.java,jtstest_geomop_test_case_geometry_functions.go,ported -geomgraph/Depth.java,geomgraph_depth.go,ported -geomgraph/DirectedEdge.java,geomgraph_directed_edge.go,ported -geomgraph/DirectedEdgeStar.java,geomgraph_directed_edge_star.go,ported -geomgraph/EdgeEnd.java,geomgraph_edge_end.go,ported -geomgraph/EdgeEndStar.java,geomgraph_edge_end_star.go,ported -geomgraph/EdgeIntersection.java,geomgraph_edge_intersection.go,ported -geomgraph/EdgeIntersectionList.java,geomgraph_edge_intersection_list.go,ported -geomgraph/Edge.java,geomgraph_edge.go,ported -geomgraph/EdgeList.java,geomgraph_edge_list.go,ported -geomgraph/EdgeNodingValidator.java,geomgraph_edge_noding_validator.go,ported -geomgraph/EdgeRing.java,geomgraph_edge_ring.go,ported -geomgraph/GeometryGraph.java,geomgraph_geometry_graph.go,ported -geomgraph/GraphComponent.java,geomgraph_graph_component.go,ported -geomgraph/index/EdgeSetIntersector.java,geomgraph_index_edge_set_intersector.go,ported -geomgraph/index/MonotoneChainEdge.java,geomgraph_index_monotone_chain_edge.go,ported -geomgraph/index/MonotoneChainIndexer.java,geomgraph_index_monotone_chain_indexer.go,ported -geomgraph/index/MonotoneChain.java,geomgraph_index_monotone_chain.go,ported -geomgraph/index/SegmentIntersector.java,geomgraph_index_segment_intersector.go,ported -geomgraph/index/SimpleEdgeSetIntersector.java,geomgraph_index_simple_edge_set_intersector.go,ported -geomgraph/index/SimpleMCSweepLineIntersector.java,geomgraph_index_simple_mc_sweep_line_intersector.go,ported -geomgraph/index/SimpleSweepLineIntersector.java,geomgraph_index_simple_sweep_line_intersector.go,ported -geomgraph/index/SweepLineEvent.java,geomgraph_index_sweep_line_event.go,ported -geomgraph/index/SweepLineSegment.java,geomgraph_index_sweep_line_segment.go,ported -geomgraph/Label.java,geomgraph_label.go,ported -geomgraph/NodeFactory.java,geomgraph_node_factory.go,ported -geomgraph/Node.java,geomgraph_node.go,ported -geomgraph/NodeMap.java,geomgraph_node_map.go,ported -geomgraph/PlanarGraph.java,geomgraph_planar_graph.go,ported -geomgraph/TopologyLocation.java,geomgraph_topology_location.go,ported -geom/impl/CoordinateArraySequenceFactory.java,geom_impl_coordinate_array_sequence_factory.go,ported -geom/impl/CoordinateArraySequence.java,geom_impl_coordinate_array_sequence.go,ported -geom/impl/CoordinateArraySequenceTest.java,geom_impl_coordinate_array_sequence_test.go,ported -geom/impl/PackedCoordinateSequence.java,geom_impl_packed_coordinate_sequence.go,ported -geom/impl/PackedCoordinateSequenceDoubleTest.java,geom_impl_packed_coordinate_sequence_double_test.go,ported -geom/impl/PackedCoordinateSequenceFactory.java,geom_impl_packed_coordinate_sequence_factory.go,ported -geom/impl/PackedCoordinateSequenceFloatTest.java,geom_impl_packed_coordinate_sequence_float_test.go,ported -geom/impl/PackedCoordinateSequenceTest.java,geom_impl_packed_coordinate_sequence_test.go,ported geom/IntersectionMatrix.java,geom_intersection_matrix.go,ported geom/IntersectionMatrixTest.java,geom_intersection_matrix_test.go,ported -geom/Lineal.java,geom_lineal.go,ported -geom/LinearRing.java,geom_linear_ring.go,ported geom/LineSegment.java,geom_line_segment.go,ported geom/LineSegmentTest.java,geom_line_segment_test.go,ported geom/LineString.java,geom_line_string.go,ported geom/LineStringTest.java,geom_line_string_test.go,ported +geom/Lineal.java,geom_lineal.go,ported +geom/LinearRing.java,geom_linear_ring.go,ported geom/Location.java,geom_location.go,ported geom/MultiLineString.java,geom_multi_line_string.go,ported geom/MultiPoint.java,geom_multi_point.go,ported @@ -129,8 +89,8 @@ geom/MultiPointTest.java,geom_multi_point_test.go,ported geom/MultiPolygon.java,geom_multi_polygon.go,ported geom/Point.java,geom_point.go,ported geom/PointTest.java,geom_point_test.go,ported -geom/Polygonal.java,geom_polygonal.go,ported geom/Polygon.java,geom_polygon.go,ported +geom/Polygonal.java,geom_polygonal.go,ported geom/Position.java,geom_position.go,ported geom/PrecisionModel.java,geom_precision_model.go,ported geom/PrecisionModelTest.java,geom_precision_model_test.go,ported @@ -139,6 +99,31 @@ geom/Quadrant.java,geom_quadrant.go,ported geom/TopologyException.java,geom_topology_exception.go,ported geom/Triangle.java,geom_triangle.go,ported geom/TriangleTest.java,geom_triangle_test.go,ported +geom/impl/CoordinateArraySequence.java,geom_impl_coordinate_array_sequence.go,ported +geom/impl/CoordinateArraySequenceFactory.java,geom_impl_coordinate_array_sequence_factory.go,ported +geom/impl/CoordinateArraySequenceTest.java,geom_impl_coordinate_array_sequence_test.go,ported +geom/impl/PackedCoordinateSequence.java,geom_impl_packed_coordinate_sequence.go,ported +geom/impl/PackedCoordinateSequenceDoubleTest.java,geom_impl_packed_coordinate_sequence_double_test.go,ported +geom/impl/PackedCoordinateSequenceFactory.java,geom_impl_packed_coordinate_sequence_factory.go,ported +geom/impl/PackedCoordinateSequenceFloatTest.java,geom_impl_packed_coordinate_sequence_float_test.go,ported +geom/impl/PackedCoordinateSequenceTest.java,geom_impl_packed_coordinate_sequence_test.go,ported +geom/prep/AbstractPreparedPolygonContains.java,geom_prep_abstract_prepared_polygon_contains.go,ported +geom/prep/BasicPreparedGeometry.java,geom_prep_basic_prepared_geometry.go,ported +geom/prep/PreparedGeometry.java,geom_prep_prepared_geometry.go,ported +geom/prep/PreparedGeometryFactory.java,geom_prep_prepared_geometry_factory.go,ported +geom/prep/PreparedGeometryTest.java,geom_prep_prepared_geometry_test.go,ported +geom/prep/PreparedLineString.java,geom_prep_prepared_line_string.go,ported +geom/prep/PreparedLineStringIntersects.java,geom_prep_prepared_line_string_intersects.go,ported +geom/prep/PreparedPoint.java,geom_prep_prepared_point.go,ported +geom/prep/PreparedPolygon.java,geom_prep_prepared_polygon.go,ported +geom/prep/PreparedPolygonContains.java,geom_prep_prepared_polygon_contains.go,ported +geom/prep/PreparedPolygonContainsProperly.java,geom_prep_prepared_polygon_contains_properly.go,ported +geom/prep/PreparedPolygonCovers.java,geom_prep_prepared_polygon_covers.go,ported +geom/prep/PreparedPolygonIntersects.java,geom_prep_prepared_polygon_intersects.go,ported +geom/prep/PreparedPolygonIntersectsStressTest.java,geom_prep_prepared_polygon_intersects_stress_test.go,ported +geom/prep/PreparedPolygonPredicate.java,geom_prep_prepared_polygon_predicate.go,ported +geom/prep/PreparedPolygonPredicateStressTest.java,geom_prep_prepared_polygon_predicate_stress_test.go,ported +geom/prep/StressTestHarness.java,geom_prep_stress_test_harness.go,ported geom/util/ComponentCoordinateExtracter.java,geom_util_component_coordinate_extracter.go,ported geom/util/GeometryCollectionMapper.java,geom_util_geometry_collection_mapper.go,ported geom/util/GeometryCombiner.java,geom_util_geometry_combiner.go,ported @@ -148,35 +133,69 @@ geom/util/GeometryExtracterTest.java,geom_util_geometry_extracter_test.go,ported geom/util/GeometryMapper.java,geom_util_geometry_mapper.go,ported geom/util/GeometryMapperTest.java,geom_util_geometry_mapper_test.go,ported geom/util/GeometryTransformer.java,geom_util_geometry_transformer.go,ported -geom/util/LinearComponentExtracter.java,geom_util_linear_component_extracter.go,ported geom/util/LineStringExtracter.java,geom_util_line_string_extracter.go,ported +geom/util/LinearComponentExtracter.java,geom_util_linear_component_extracter.go,ported geom/util/PointExtracter.java,geom_util_point_extracter.go,ported -geom/util/PolygonalExtracter.java,geom_util_polygonal_extracter.go,ported geom/util/PolygonExtracter.java,geom_util_polygon_extracter.go,ported +geom/util/PolygonalExtracter.java,geom_util_polygonal_extracter.go,ported geom/util/ShortCircuitedGeometryVisitor.java,geom_util_short_circuited_geometry_visitor.go,ported +geom/util/SineStarFactory.java,geom_util_sine_star_factory.go,ported +geomgraph/Depth.java,geomgraph_depth.go,ported +geomgraph/DirectedEdge.java,geomgraph_directed_edge.go,ported +geomgraph/DirectedEdgeStar.java,geomgraph_directed_edge_star.go,ported +geomgraph/Edge.java,geomgraph_edge.go,ported +geomgraph/EdgeEnd.java,geomgraph_edge_end.go,ported +geomgraph/EdgeEndStar.java,geomgraph_edge_end_star.go,ported +geomgraph/EdgeIntersection.java,geomgraph_edge_intersection.go,ported +geomgraph/EdgeIntersectionList.java,geomgraph_edge_intersection_list.go,ported +geomgraph/EdgeList.java,geomgraph_edge_list.go,ported +geomgraph/EdgeNodingValidator.java,geomgraph_edge_noding_validator.go,ported +geomgraph/EdgeRing.java,geomgraph_edge_ring.go,ported +geomgraph/GeometryGraph.java,geomgraph_geometry_graph.go,ported +geomgraph/GraphComponent.java,geomgraph_graph_component.go,ported +geomgraph/Label.java,geomgraph_label.go,ported +geomgraph/Node.java,geomgraph_node.go,ported +geomgraph/NodeFactory.java,geomgraph_node_factory.go,ported +geomgraph/NodeMap.java,geomgraph_node_map.go,ported +geomgraph/PlanarGraph.java,geomgraph_planar_graph.go,ported +geomgraph/TopologyLocation.java,geomgraph_topology_location.go,ported +geomgraph/index/EdgeSetIntersector.java,geomgraph_index_edge_set_intersector.go,ported +geomgraph/index/MonotoneChain.java,geomgraph_index_monotone_chain.go,ported +geomgraph/index/MonotoneChainEdge.java,geomgraph_index_monotone_chain_edge.go,ported +geomgraph/index/MonotoneChainIndexer.java,geomgraph_index_monotone_chain_indexer.go,ported +geomgraph/index/SegmentIntersector.java,geomgraph_index_segment_intersector.go,ported +geomgraph/index/SimpleEdgeSetIntersector.java,geomgraph_index_simple_edge_set_intersector.go,ported +geomgraph/index/SimpleMCSweepLineIntersector.java,geomgraph_index_simple_mc_sweep_line_intersector.go,ported +geomgraph/index/SimpleSweepLineIntersector.java,geomgraph_index_simple_sweep_line_intersector.go,ported +geomgraph/index/SweepLineEvent.java,geomgraph_index_sweep_line_event.go,ported +geomgraph/index/SweepLineSegment.java,geomgraph_index_sweep_line_segment.go,ported +geomop/GeometryMethodOperation.java,jtstest_geomop_geometry_method_operation.go,ported +geomop/GeometryOperation.java,jtstest_geomop_geometry_operation.go,ported +geomop/PreparedGeometryOperation.java,jtstest_geomop_prepared_geometry_operation.go,ported +geomop/TestCaseGeometryFunctions.java,jtstest_geomop_test_case_geometry_functions.go,ported index/ArrayListVisitor.java,index_array_list_visitor.go,ported -index/chain/MonotoneChainBuilder.java,index_chain_monotone_chain_builder.go,ported +index/ItemVisitor.java,index_item_visitor.go,ported +index/SpatialIndex.java,index_spatial_index.go,ported index/chain/MonotoneChain.java,index_chain_monotone_chain.go,ported +index/chain/MonotoneChainBuilder.java,index_chain_monotone_chain_builder.go,ported index/chain/MonotoneChainOverlapAction.java,index_chain_monotone_chain_overlap_action.go,ported index/chain/MonotoneChainSelectAction.java,index_chain_monotone_chain_select_action.go,ported -index/hprtree/HilbertEncoder.java,index_hprtree_hilbert_encoder.go,ported index/hprtree/HPRtree.java,index_hprtree_hprtree.go,ported index/hprtree/HPRtreeTest.java,index_hprtree_hprtree_test.go,ported +index/hprtree/HilbertEncoder.java,index_hprtree_hilbert_encoder.go,ported index/hprtree/Item.java,index_hprtree_item.go,ported index/intervalrtree/IntervalRTreeBranchNode.java,index_intervalrtree_interval_rtree_branch_node.go,ported index/intervalrtree/IntervalRTreeLeafNode.java,index_intervalrtree_interval_rtree_leaf_node.go,ported index/intervalrtree/IntervalRTreeNode.java,index_intervalrtree_interval_rtree_node.go,ported index/intervalrtree/SortedPackedIntervalRTree.java,index_intervalrtree_sorted_packed_interval_rtree.go,ported index/intervalrtree/SortedPackedIntervalRTreeTest.java,index_intervalrtree_sorted_packed_interval_rtree_test.go,ported -index/ItemVisitor.java,index_item_visitor.go,ported index/kdtree/KdNode.java,index_kdtree_kd_node.go,ported index/kdtree/KdTree.java,index_kdtree_kd_tree.go,ported -index/SpatialIndex.java,index_spatial_index.go,ported index/strtree/AbstractNode.java,index_strtree_abstract_node.go,ported index/strtree/AbstractSTRtree.java,index_strtree_abstract_strtree.go,ported index/strtree/Boundable.java,index_strtree_boundable.go,ported -index/strtree/BoundablePairDistanceComparator.java,index_strtree_boundable_pair_distance_comparator.go,ported index/strtree/BoundablePair.java,index_strtree_boundable_pair.go,ported +index/strtree/BoundablePairDistanceComparator.java,index_strtree_boundable_pair_distance_comparator.go,ported index/strtree/EnvelopeDistance.java,index_strtree_envelope_distance.go,ported index/strtree/EnvelopeDistanceTest.java,index_strtree_envelope_distance_test.go,ported index/strtree/GeometryItemDistance.java,index_strtree_geometry_item_distance.go,ported @@ -192,11 +211,11 @@ io/ByteArrayInStream.java,io_byte_array_in_stream.go,ported io/ByteOrderDataInStream.java,io_byte_order_data_in_stream.go,ported io/ByteOrderValues.java,io_byte_order_values.go,ported io/InStream.java,io_in_stream.go,ported +io/Ordinate.java,io_ordinate.go,ported io/OrdinateFormat.java,io_ordinate_format.go,ported io/OrdinateFormatTest.java,io_ordinate_format_test.go,ported -io/Ordinate.java,io_ordinate.go,ported -io/OutputStreamOutStream.java,io_output_stream_out_stream.go,ported io/OutStream.java,io_out_stream.go,ported +io/OutputStreamOutStream.java,io_output_stream_out_stream.go,ported io/ParseException.java,io_parse_exception.go,ported io/WKBConstants.java,io_wkb_constants.go,ported io/WKBReader.java,io_wkb_reader.go,ported @@ -219,6 +238,7 @@ noding/BasicSegmentString.java,noding_basic_segment_string.go,ported noding/BoundaryChainNoder.java,noding_boundary_chain_noder.go,ported noding/FastNodingValidator.java,noding_fast_noding_validator.go,ported noding/FastNodingValidatorTest.java,noding_fast_noding_validator_test.go,ported +noding/FastSegmentSetIntersectionFinder.java,noding_fast_segment_set_intersection_finder.go,ported noding/InteriorIntersectionFinderAdder.java,noding_interior_intersection_finder_adder.go,ported noding/IntersectionAdder.java,noding_intersection_adder.go,ported noding/IntersectionFinderAdder.java,noding_intersection_finder_adder.go,ported @@ -233,6 +253,7 @@ noding/NodingValidator.java,noding_noding_validator.go,ported noding/Octant.java,noding_octant.go,ported noding/ScaledNoder.java,noding_scaled_noder.go,ported noding/SegmentExtractingNoder.java,noding_segment_extracting_noder.go,ported +noding/SegmentIntersectionDetector.java,noding_segment_intersection_detector.go,ported noding/SegmentIntersector.java,noding_segment_intersector.go,ported noding/SegmentNode.java,noding_segment_node.go,ported noding/SegmentNodeList.java,noding_segment_node_list.go,ported @@ -240,11 +261,17 @@ noding/SegmentPointComparator.java,noding_segment_point_comparator.go,ported noding/SegmentPointComparatorTest.java,noding_segment_point_comparator_test.go,ported noding/SegmentSetMutualIntersector.java,noding_segment_set_mutual_intersector.go,ported noding/SegmentString.java,noding_segment_string.go,ported +noding/SegmentStringUtil.java,noding_segment_string_util.go,ported noding/SinglePassNoder.java,noding_single_pass_noder.go,ported noding/TestUtil.java,noding_test_util_test.go,ported +noding/ValidatingNoder.java,noding_validating_noder.go,ported +noding/snap/SnappingIntersectionAdder.java,noding_snap_snapping_intersection_adder.go,ported +noding/snap/SnappingNoder.java,noding_snap_snapping_noder.go,ported +noding/snap/SnappingNoderTest.java,noding_snap_snapping_noder_test.go,ported +noding/snap/SnappingPointIndex.java,noding_snap_snapping_point_index.go,ported noding/snapround/GeometryNoder.java,noding_snapround_geometry_noder.go,ported -noding/snapround/HotPixelIndex.java,noding_snapround_hot_pixel_index.go,ported noding/snapround/HotPixel.java,noding_snapround_hot_pixel.go,ported +noding/snapround/HotPixelIndex.java,noding_snapround_hot_pixel_index.go,ported noding/snapround/HotPixelTest.java,noding_snapround_hot_pixel_test.go,ported noding/snapround/MCIndexPointSnapper.java,noding_snapround_mc_index_point_snapper.go,ported noding/snapround/MCIndexSnapRounder.java,noding_snapround_mc_index_snap_rounder.go,ported @@ -253,22 +280,18 @@ noding/snapround/SnapRoundingIntersectionAdder.java,noding_snapround_snap_roundi noding/snapround/SnapRoundingNoder.java,noding_snapround_snap_rounding_noder.go,ported noding/snapround/SnapRoundingNoderTest.java,noding_snapround_snap_rounding_noder_test.go,ported noding/snapround/SnapRoundingTest.java,noding_snapround_snap_rounding_test.go,ported -noding/snap/SnappingIntersectionAdder.java,noding_snap_snapping_intersection_adder.go,ported -noding/snap/SnappingNoder.java,noding_snap_snapping_noder.go,ported -noding/snap/SnappingNoderTest.java,noding_snap_snapping_noder_test.go,ported -noding/snap/SnappingPointIndex.java,noding_snap_snapping_point_index.go,ported -noding/ValidatingNoder.java,noding_validating_noder.go,ported operation/BoundaryOp.java,operation_boundary_op.go,ported +operation/GeometryGraphOperation.java,operation_geometry_graph_operation.go,ported operation/buffer/BufferBuilder.java,operation_buffer_buffer_builder.go,ported operation/buffer/BufferCurveSetBuilder.java,operation_buffer_buffer_curve_set_builder.go,ported operation/buffer/BufferInputLineSimplifier.java,operation_buffer_buffer_input_line_simplifier.go,ported operation/buffer/BufferOp.java,operation_buffer_buffer_op.go,ported -operation/buffer/BufferParameters.java,operation_buffer_buffer_parameters.go,ported operation/buffer/BufferParameterTest.java,operation_buffer_buffer_parameter_test.go,ported +operation/buffer/BufferParameters.java,operation_buffer_buffer_parameters.go,ported operation/buffer/BufferResultValidatorTest.java,operation_buffer_buffer_result_validator_test.go,ported -operation/buffer/BufferValidator.java,operation_buffer_buffer_validator_test.go,ported operation/buffer/BufferSubgraph.java,operation_buffer_buffer_subgraph.go,ported operation/buffer/BufferTest.java,operation_buffer_buffer_test.go,ported +operation/buffer/BufferValidator.java,operation_buffer_buffer_validator_test.go,ported operation/buffer/DepthSegmentTest.java,operation_buffer_depth_segment_test.go,ported operation/buffer/OffsetCurve.java,operation_buffer_offset_curve.go,ported operation/buffer/OffsetCurveBuilder.java,operation_buffer_offset_curve_builder.go,ported @@ -292,7 +315,6 @@ operation/distance/ConnectedElementPointFilter.java,operation_distance_connected operation/distance/DistanceOp.java,operation_distance_distance_op.go,ported operation/distance/DistanceTest.java,operation_distance_distance_test.go,ported operation/distance/GeometryLocation.java,operation_distance_geometry_location.go,ported -operation/GeometryGraphOperation.java,operation_geometry_graph_operation.go,ported operation/linemerge/EdgeString.java,operation_linemerge_edge_string.go,ported operation/linemerge/LineMergeDirectedEdge.java,operation_linemerge_line_merge_directed_edge.go,ported operation/linemerge/LineMergeEdge.java,operation_linemerge_line_merge_edge.go,ported @@ -307,7 +329,15 @@ operation/overlay/FixedPrecisionSnapRoundingTest.java,operation_overlay_fixed_pr operation/overlay/LineBuilder.java,operation_overlay_line_builder.go,ported operation/overlay/MaximalEdgeRing.java,operation_overlay_maximal_edge_ring.go,ported operation/overlay/MinimalEdgeRing.java,operation_overlay_minimal_edge_ring.go,ported +operation/overlay/OverlayNodeFactory.java,operation_overlay_overlay_node_factory.go,ported +operation/overlay/OverlayOp.java,operation_overlay_overlay_op.go,ported operation/overlay/OverlayOpTest.java,operation_overlay_overlay_op_test.go,ported +operation/overlay/PointBuilder.java,operation_overlay_point_builder.go,ported +operation/overlay/PolygonBuilder.java,operation_overlay_polygon_builder.go,ported +operation/overlay/snap/GeometrySnapper.java,operation_overlay_snap_geometry_snapper.go,ported +operation/overlay/snap/LineStringSnapper.java,operation_overlay_snap_line_string_snapper.go,ported +operation/overlay/snap/SnapIfNeededOverlayOp.java,operation_overlay_snap_snap_if_needed_overlay_op.go,ported +operation/overlay/snap/SnapOverlayOp.java,operation_overlay_snap_snap_overlay_op.go,ported operation/overlayng/CoverageUnion.java,operation_overlayng_coverage_union.go,ported operation/overlayng/CoverageUnionTest.java,operation_overlayng_coverage_union_test.go,ported operation/overlayng/Edge.java,operation_overlayng_edge.go,ported @@ -348,20 +378,16 @@ operation/overlayng/RingClipperTest.java,operation_overlayng_ring_clipper_test.g operation/overlayng/RobustClipEnvelopeComputer.java,operation_overlayng_robust_clip_envelope_computer.go,ported operation/overlayng/UnaryUnionNG.java,operation_overlayng_unary_union_ng.go,ported operation/overlayng/UnaryUnionNGTest.java,operation_overlayng_unary_union_ng_test.go,ported -operation/overlay/OverlayNodeFactory.java,operation_overlay_overlay_node_factory.go,ported -operation/overlay/OverlayOp.java,operation_overlay_overlay_op.go,ported -operation/overlay/PointBuilder.java,operation_overlay_point_builder.go,ported -operation/overlay/PolygonBuilder.java,operation_overlay_polygon_builder.go,ported -operation/overlay/snap/GeometrySnapper.java,operation_overlay_snap_geometry_snapper.go,ported -operation/overlay/snap/LineStringSnapper.java,operation_overlay_snap_line_string_snapper.go,ported -operation/overlay/snap/SnapIfNeededOverlayOp.java,operation_overlay_snap_snap_if_needed_overlay_op.go,ported -operation/overlay/snap/SnapOverlayOp.java,operation_overlay_snap_snap_overlay_op.go,ported operation/predicate/RectangleContains.java,operation_predicate_rectangle_contains.go,ported operation/predicate/RectangleIntersects.java,operation_predicate_rectangle_intersects.go,ported operation/predicate/RectangleIntersectsTest.java,operation_predicate_rectangle_intersects_test.go,ported operation/relate/EdgeEndBuilder.java,operation_relate_edge_end_builder.go,ported operation/relate/EdgeEndBundle.java,operation_relate_edge_end_bundle.go,ported operation/relate/EdgeEndBundleStar.java,operation_relate_edge_end_bundle_star.go,ported +operation/relate/RelateComputer.java,operation_relate_relate_computer.go,ported +operation/relate/RelateNode.java,operation_relate_relate_node.go,ported +operation/relate/RelateNodeFactory.java,operation_relate_relate_node_factory.go,ported +operation/relate/RelateOp.java,operation_relate_relate_op.go,ported operation/relateng/AdjacentEdgeLocator.java,operation_relateng_adjacent_edge_locator.go,ported operation/relateng/AdjacentEdgeLocatorTest.java,operation_relateng_adjacent_edge_locator_test.go,ported operation/relateng/BasicPredicate.java,operation_relateng_basic_predicate.go,ported @@ -392,10 +418,6 @@ operation/relateng/RelateSegmentString.java,operation_relateng_relate_segment_st operation/relateng/TopologyComputer.java,operation_relateng_topology_computer.go,ported operation/relateng/TopologyPredicate.java,operation_relateng_topology_predicate.go,ported operation/relateng/TopologyPredicateTracer.java,operation_relateng_topology_predicate_tracer.go,ported -operation/relate/RelateComputer.java,operation_relate_relate_computer.go,ported -operation/relate/RelateNodeFactory.java,operation_relate_relate_node_factory.go,ported -operation/relate/RelateNode.java,operation_relate_relate_node.go,ported -operation/relate/RelateOp.java,operation_relate_relate_op.go,ported operation/union/CascadedPolygonUnion.java,operation_union_cascaded_polygon_union.go,ported operation/union/CascadedPolygonUnionTest.java,operation_union_cascaded_polygon_union_test.go,ported operation/union/InputExtracter.java,operation_union_input_extracter.go,ported @@ -412,13 +434,12 @@ operation/valid/IsSimpleOp.java,operation_valid_is_simple_op.go,ported operation/valid/IsSimpleOpTest.java,operation_valid_is_simple_op_test.go,ported operation/valid/IsValidOp.java,operation_valid_is_valid_op.go,ported operation/valid/IsValidTest.java,operation_valid_is_valid_test.go,ported -operation/valid/ValidClosedRingTest.java,operation_valid_valid_closed_ring_test.go,ported -operation/valid/ValidSelfTouchingRingTest.java,operation_valid_valid_self_touching_ring_test.go,ported operation/valid/PolygonIntersectionAnalyzer.java,operation_valid_polygon_intersection_analyzer.go,ported operation/valid/PolygonRing.java,operation_valid_polygon_ring.go,ported operation/valid/PolygonTopologyAnalyzer.java,operation_valid_polygon_topology_analyzer.go,ported operation/valid/TopologyValidationError.java,operation_valid_topology_validation_error.go,ported -planargraph/algorithm/ConnectedSubgraphFinder.java,planargraph_algorithm_connected_subgraph_finder.go,ported +operation/valid/ValidClosedRingTest.java,operation_valid_valid_closed_ring_test.go,ported +operation/valid/ValidSelfTouchingRingTest.java,operation_valid_valid_self_touching_ring_test.go,ported planargraph/DirectedEdge.java,planargraph_directed_edge.go,ported planargraph/DirectedEdgeStar.java,planargraph_directed_edge_star.go,ported planargraph/DirectedEdgeTest.java,planargraph_directed_edge_test.go,ported @@ -428,6 +449,7 @@ planargraph/Node.java,planargraph_node.go,ported planargraph/NodeMap.java,planargraph_node_map.go,ported planargraph/PlanarGraph.java,planargraph_planar_graph.go,ported planargraph/Subgraph.java,planargraph_subgraph.go,ported +planargraph/algorithm/ConnectedSubgraphFinder.java,planargraph_algorithm_connected_subgraph_finder.go,ported shape/fractal/HilbertCode.java,shape_fractal_hilbert_code.go,ported shape/fractal/HilbertCodeTest.java,shape_fractal_hilbert_code_test.go,ported testrunner/BooleanResult.java,jtstest_testrunner_boolean_result.go,ported @@ -444,9 +466,10 @@ testrunner/TestCase.java,jtstest_testrunner_test_case.go,ported testrunner/TestParseException.java,jtstest_testrunner_test_parse_exception.go,ported testrunner/TestReader.java,jtstest_testrunner_test_reader.go,ported testrunner/TestRun.java,jtstest_testrunner_test_run.go,ported -util/AssertionFailedException.java,util_assertion_failed_exception.go,reviewed util/Assert.java,util_assert.go,reviewed +util/AssertionFailedException.java,util_assertion_failed_exception.go,reviewed util/Debug.java,util_debug.go,ported +util/GeometricShapeFactory.java,util_geometric_shape_factory.go,ported util/IntArrayList.java,util_int_array_list.go,reviewed util/IntArrayListTest.java,util_int_array_list_test.go,reviewed util/Stopwatch.java,util_stopwatch.go,ported diff --git a/internal/jtsport/jts/geom_prep_abstract_prepared_polygon_contains.go b/internal/jtsport/jts/geom_prep_abstract_prepared_polygon_contains.go new file mode 100644 index 00000000..464bb8e3 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_abstract_prepared_polygon_contains.go @@ -0,0 +1,122 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_AbstractPreparedPolygonContains struct { + *GeomPrep_PreparedPolygonPredicate + child java.Polymorphic + requireSomePointInInterior bool + + // information about geometric situation + hasSegmentIntersection bool + hasProperIntersection bool + hasNonProperIntersection bool +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) GetChild() java.Polymorphic { return a.child } +func (a *GeomPrep_AbstractPreparedPolygonContains) GetParent() java.Polymorphic { return nil } + +func geomPrep_NewAbstractPreparedPolygonContains(prepPoly *GeomPrep_PreparedPolygon) *GeomPrep_AbstractPreparedPolygonContains { + base := geomPrep_NewPreparedPolygonPredicate(prepPoly) + a := &GeomPrep_AbstractPreparedPolygonContains{ + GeomPrep_PreparedPolygonPredicate: base, + requireSomePointInInterior: true, + } + base.child = a + return a +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) eval(geom *Geom_Geometry) bool { + if geom.GetDimension() == 0 { + return a.evalPoints(geom) + } + + isAllInTargetArea := a.isAllTestComponentsInTarget(geom) + if !isAllInTargetArea { + return false + } + + properIntersectionImpliesNotContained := a.isProperIntersectionImpliesNotContainedSituation(geom) + + // find all intersection types which exist + a.findAndClassifyIntersections(geom) + + if properIntersectionImpliesNotContained && a.hasProperIntersection { + return false + } + + if a.hasSegmentIntersection && !a.hasNonProperIntersection { + return false + } + + if a.hasSegmentIntersection { + return a.fullTopologicalPredicate(geom) + } + + if java.InstanceOf[Geom_Polygonal](geom) { + // TODO: generalize this to handle GeometryCollections + isTargetInTestArea := a.isAnyTargetComponentInAreaTest(geom, a.prepPoly.GetRepresentativePoints()) + if isTargetInTestArea { + return false + } + } + return true +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) evalPoints(geom *Geom_Geometry) bool { + isAllInTargetArea := a.isAllTestPointsInTarget(geom) + if !isAllInTargetArea { + return false + } + + if a.requireSomePointInInterior { + isAnyInTargetInterior := a.isAnyTestPointInTargetInterior(geom) + return isAnyInTargetInterior + } + return true +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) isProperIntersectionImpliesNotContainedSituation(testGeom *Geom_Geometry) bool { + if java.InstanceOf[Geom_Polygonal](testGeom) { + return true + } + if a.isSingleShell(a.prepPoly.GetGeometry()) { + return true + } + return false +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) isSingleShell(geom *Geom_Geometry) bool { + // handles single-element MultiPolygons, as well as Polygons + if geom.GetNumGeometries() != 1 { + return false + } + + poly := java.Cast[*Geom_Polygon](geom.GetGeometryN(0)) + numHoles := poly.GetNumInteriorRing() + if numHoles == 0 { + return true + } + return false +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) findAndClassifyIntersections(geom *Geom_Geometry) { + lineSegStr := Noding_SegmentStringUtil_ExtractSegmentStrings(geom) + + intDetector := Noding_NewSegmentIntersectionDetector() + intDetector.SetFindAllIntersectionTypes(true) + a.prepPoly.GetIntersectionFinder().IntersectsWithDetector(lineSegStr, intDetector) + + a.hasSegmentIntersection = intDetector.HasIntersection() + a.hasProperIntersection = intDetector.HasProperIntersection() + a.hasNonProperIntersection = intDetector.HasNonProperIntersection() +} + +func (a *GeomPrep_AbstractPreparedPolygonContains) fullTopologicalPredicate(geom *Geom_Geometry) bool { + if impl, ok := java.GetLeaf(a).(interface { + FullTopologicalPredicate_BODY(*Geom_Geometry) bool + }); ok { + return impl.FullTopologicalPredicate_BODY(geom) + } + panic("abstract method called") +} diff --git a/internal/jtsport/jts/geom_prep_basic_prepared_geometry.go b/internal/jtsport/jts/geom_prep_basic_prepared_geometry.go new file mode 100644 index 00000000..dd9a66f6 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_basic_prepared_geometry.go @@ -0,0 +1,139 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +var _ GeomPrep_PreparedGeometry = (*GeomPrep_BasicPreparedGeometry)(nil) + +type GeomPrep_BasicPreparedGeometry struct { + child java.Polymorphic + baseGeom *Geom_Geometry + representativePts []*Geom_Coordinate +} + +func (b *GeomPrep_BasicPreparedGeometry) IsGeomPrep_PreparedGeometry() {} + +func (b *GeomPrep_BasicPreparedGeometry) GetChild() java.Polymorphic { return b.child } + +func (b *GeomPrep_BasicPreparedGeometry) GetParent() java.Polymorphic { return nil } + +func GeomPrep_NewBasicPreparedGeometry(geom *Geom_Geometry) *GeomPrep_BasicPreparedGeometry { + return &GeomPrep_BasicPreparedGeometry{ + baseGeom: geom, + representativePts: GeomUtil_ComponentCoordinateExtracter_GetCoordinates(geom), + } +} + +func (b *GeomPrep_BasicPreparedGeometry) GetGeometry() *Geom_Geometry { return b.baseGeom } + +func (b *GeomPrep_BasicPreparedGeometry) GetRepresentativePoints() []*Geom_Coordinate { + //TODO wrap in unmodifiable? + return b.representativePts +} + +func (b *GeomPrep_BasicPreparedGeometry) IsAnyTargetComponentInTest(testGeom *Geom_Geometry) bool { + locator := Algorithm_NewPointLocator() + for _, p := range b.representativePts { + if locator.Intersects(p, testGeom) { + return true + } + } + return false +} + +func (b *GeomPrep_BasicPreparedGeometry) envelopesIntersect(g *Geom_Geometry) bool { + if !b.baseGeom.GetEnvelopeInternal().IntersectsEnvelope(g.GetEnvelopeInternal()) { + return false + } + return true +} + +func (b *GeomPrep_BasicPreparedGeometry) envelopeCovers(g *Geom_Geometry) bool { + if !b.baseGeom.GetEnvelopeInternal().CoversEnvelope(g.GetEnvelopeInternal()) { + return false + } + return true +} + +// Contains dispatcher - overridden in PreparedPolygon. +func (b *GeomPrep_BasicPreparedGeometry) Contains(g *Geom_Geometry) bool { + if impl, ok := java.GetLeaf(b).(interface{ Contains_BODY(*Geom_Geometry) bool }); ok { + return impl.Contains_BODY(g) + } + return b.Contains_BODY(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Contains_BODY(g *Geom_Geometry) bool { + return b.baseGeom.Contains(g) +} + +// ContainsProperly dispatcher - overridden in PreparedPolygon. +func (b *GeomPrep_BasicPreparedGeometry) ContainsProperly(g *Geom_Geometry) bool { + if impl, ok := java.GetLeaf(b).(interface{ ContainsProperly_BODY(*Geom_Geometry) bool }); ok { + return impl.ContainsProperly_BODY(g) + } + return b.ContainsProperly_BODY(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) ContainsProperly_BODY(g *Geom_Geometry) bool { + // since raw relate is used, provide some optimizations + + // short-circuit test + if !b.baseGeom.GetEnvelopeInternal().ContainsEnvelope(g.GetEnvelopeInternal()) { + return false + } + + // otherwise, compute using relate mask + return b.baseGeom.Relate(g, "T**FF*FF*") +} + +func (b *GeomPrep_BasicPreparedGeometry) CoveredBy(g *Geom_Geometry) bool { + return b.baseGeom.CoveredBy(g) +} + +// Covers dispatcher - overridden in PreparedPolygon. +func (b *GeomPrep_BasicPreparedGeometry) Covers(g *Geom_Geometry) bool { + if impl, ok := java.GetLeaf(b).(interface{ Covers_BODY(*Geom_Geometry) bool }); ok { + return impl.Covers_BODY(g) + } + return b.Covers_BODY(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Covers_BODY(g *Geom_Geometry) bool { + return b.baseGeom.Covers(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Crosses(g *Geom_Geometry) bool { + return b.baseGeom.Crosses(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Disjoint(g *Geom_Geometry) bool { + return !b.Intersects(g) +} + +// Intersects dispatcher - overridden in PreparedPoint, PreparedLineString, PreparedPolygon. +func (b *GeomPrep_BasicPreparedGeometry) Intersects(g *Geom_Geometry) bool { + if impl, ok := java.GetLeaf(b).(interface{ Intersects_BODY(*Geom_Geometry) bool }); ok { + return impl.Intersects_BODY(g) + } + return b.Intersects_BODY(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Intersects_BODY(g *Geom_Geometry) bool { + return b.baseGeom.Intersects(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Overlaps(g *Geom_Geometry) bool { + return b.baseGeom.Overlaps(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Touches(g *Geom_Geometry) bool { + return b.baseGeom.Touches(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) Within(g *Geom_Geometry) bool { + return b.baseGeom.Within(g) +} + +func (b *GeomPrep_BasicPreparedGeometry) String() string { + return b.baseGeom.String() +} diff --git a/internal/jtsport/jts/geom_prep_prepared_geometry.go b/internal/jtsport/jts/geom_prep_prepared_geometry.go new file mode 100644 index 00000000..ac9632c1 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_geometry.go @@ -0,0 +1,16 @@ +package jts + +type GeomPrep_PreparedGeometry interface { + IsGeomPrep_PreparedGeometry() + GetGeometry() *Geom_Geometry + Contains(geom *Geom_Geometry) bool + ContainsProperly(geom *Geom_Geometry) bool + CoveredBy(geom *Geom_Geometry) bool + Covers(geom *Geom_Geometry) bool + Crosses(geom *Geom_Geometry) bool + Disjoint(geom *Geom_Geometry) bool + Intersects(geom *Geom_Geometry) bool + Overlaps(geom *Geom_Geometry) bool + Touches(geom *Geom_Geometry) bool + Within(geom *Geom_Geometry) bool +} diff --git a/internal/jtsport/jts/geom_prep_prepared_geometry_factory.go b/internal/jtsport/jts/geom_prep_prepared_geometry_factory.go new file mode 100644 index 00000000..b3897f0e --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_geometry_factory.go @@ -0,0 +1,28 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedGeometryFactory struct { +} + +func GeomPrep_PreparedGeometryFactory_Prepare(geom *Geom_Geometry) GeomPrep_PreparedGeometry { + return GeomPrep_NewPreparedGeometryFactory().Create(geom) +} + +func GeomPrep_NewPreparedGeometryFactory() *GeomPrep_PreparedGeometryFactory { + return &GeomPrep_PreparedGeometryFactory{} +} + +func (f *GeomPrep_PreparedGeometryFactory) Create(geom *Geom_Geometry) GeomPrep_PreparedGeometry { + if java.InstanceOf[Geom_Polygonal](geom) { + return GeomPrep_NewPreparedPolygon(java.GetLeaf(geom).(Geom_Polygonal)) + } + if java.InstanceOf[Geom_Lineal](geom) { + return GeomPrep_NewPreparedLineString(java.GetLeaf(geom).(Geom_Lineal)) + } + if java.InstanceOf[Geom_Puntal](geom) { + return GeomPrep_NewPreparedPoint(java.GetLeaf(geom).(Geom_Puntal)) + } + + return GeomPrep_NewBasicPreparedGeometry(geom) +} diff --git a/internal/jtsport/jts/geom_prep_prepared_geometry_test.go b/internal/jtsport/jts/geom_prep_prepared_geometry_test.go new file mode 100644 index 00000000..66d6e1e7 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_geometry_test.go @@ -0,0 +1,30 @@ +package jts + +import ( + "testing" + + "github.com/peterstace/simplefeatures/internal/jtsport/junit" +) + +// TRANSLITERATION NOTE: Java main() method (JUnit TestRunner entry point) not +// ported - Go uses `go test`. + +// TRANSLITERATION NOTE: Java constructor +// PreparedGeometryTest(String name) not ported - JUnit TestCase infrastructure +// not needed in Go. + +func TestPreparedGeometryEmptyElement(t *testing.T) { + reader := Io_NewWKTReader() + geomA, err := reader.Read("MULTIPOLYGON (((9 9, 9 1, 1 1, 2 4, 7 7, 9 9)), EMPTY)") + if err != nil { + t.Fatalf("failed to read geomA: %v", err) + } + geomB, err := reader.Read("MULTIPOLYGON (((7 6, 7 3, 4 3, 7 6)), EMPTY)") + if err != nil { + t.Fatalf("failed to read geomB: %v", err) + } + prepA := GeomPrep_PreparedGeometryFactory_Prepare(geomA) + junit.AssertTrue(t, prepA.Covers(geomB)) + junit.AssertTrue(t, prepA.Contains(geomB)) + junit.AssertTrue(t, prepA.Intersects(geomB)) +} diff --git a/internal/jtsport/jts/geom_prep_prepared_line_string.go b/internal/jtsport/jts/geom_prep_prepared_line_string.go new file mode 100644 index 00000000..ddc640e2 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_line_string.go @@ -0,0 +1,40 @@ +package jts + +import ( + "sync" + + "github.com/peterstace/simplefeatures/internal/jtsport/java" +) + +type GeomPrep_PreparedLineString struct { + *GeomPrep_BasicPreparedGeometry + segIntFinder *Noding_FastSegmentSetIntersectionFinder + // TRANSLITERATION NOTE: sync.Mutex replaces Java's synchronized keyword. + mu sync.Mutex +} + +func GeomPrep_NewPreparedLineString(line Geom_Lineal) *GeomPrep_PreparedLineString { + base := GeomPrep_NewBasicPreparedGeometry(java.Cast[*Geom_Geometry](line.(java.Polymorphic))) + pls := &GeomPrep_PreparedLineString{GeomPrep_BasicPreparedGeometry: base} + base.child = pls + return pls +} + +func (p *GeomPrep_PreparedLineString) GetChild() java.Polymorphic { return nil } + +// TRANSLITERATION NOTE: sync.Mutex replaces Java's synchronized keyword. +func (p *GeomPrep_PreparedLineString) GetIntersectionFinder() *Noding_FastSegmentSetIntersectionFinder { + p.mu.Lock() + defer p.mu.Unlock() + if p.segIntFinder == nil { + p.segIntFinder = Noding_NewFastSegmentSetIntersectionFinder(Noding_SegmentStringUtil_ExtractSegmentStrings(p.GetGeometry())) + } + return p.segIntFinder +} + +func (p *GeomPrep_PreparedLineString) Intersects_BODY(g *Geom_Geometry) bool { + if !p.envelopesIntersect(g) { + return false + } + return GeomPrep_PreparedLineStringIntersects_Intersects(p, g) +} diff --git a/internal/jtsport/jts/geom_prep_prepared_line_string_intersects.go b/internal/jtsport/jts/geom_prep_prepared_line_string_intersects.go new file mode 100644 index 00000000..f00cc448 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_line_string_intersects.go @@ -0,0 +1,51 @@ +package jts + +func GeomPrep_PreparedLineStringIntersects_Intersects(prep *GeomPrep_PreparedLineString, geom *Geom_Geometry) bool { + op := GeomPrep_NewPreparedLineStringIntersects(prep) + return op.Intersects(geom) +} + +type GeomPrep_PreparedLineStringIntersects struct { + prepLine *GeomPrep_PreparedLineString +} + +func GeomPrep_NewPreparedLineStringIntersects(prepLine *GeomPrep_PreparedLineString) *GeomPrep_PreparedLineStringIntersects { + return &GeomPrep_PreparedLineStringIntersects{prepLine: prepLine} +} + +func (op *GeomPrep_PreparedLineStringIntersects) Intersects(geom *Geom_Geometry) bool { + // If any segments intersect, obviously intersects = true + lineSegStr := Noding_SegmentStringUtil_ExtractSegmentStrings(geom) + // only request intersection finder if there are segments (ie NOT for point inputs) + if len(lineSegStr) > 0 { + segsIntersect := op.prepLine.GetIntersectionFinder().Intersects(lineSegStr) + if segsIntersect { + return true + } + } + + // For L/A case, need to check for proper inclusion of the target in the test + if geom.GetDimension() == 2 && op.prepLine.IsAnyTargetComponentInTest(geom) { + return true + } + + // For L/P case, need to check if any points lie on line(s) + if geom.HasDimension(0) { + return op.isAnyTestPointInTarget(geom) + } + + return false +} + +func (op *GeomPrep_PreparedLineStringIntersects) isAnyTestPointInTarget(testGeom *Geom_Geometry) bool { + // This could be optimized by using the segment index on the lineal target. + // However, it seems like the L/P case would be pretty rare in practice. + locator := Algorithm_NewPointLocator() + coords := GeomUtil_ComponentCoordinateExtracter_GetCoordinates(testGeom) + for _, p := range coords { + if locator.Intersects(p, op.prepLine.GetGeometry()) { + return true + } + } + return false +} diff --git a/internal/jtsport/jts/geom_prep_prepared_point.go b/internal/jtsport/jts/geom_prep_prepared_point.go new file mode 100644 index 00000000..d5ed52ae --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_point.go @@ -0,0 +1,24 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedPoint struct { + *GeomPrep_BasicPreparedGeometry +} + +func (p *GeomPrep_PreparedPoint) GetChild() java.Polymorphic { return nil } + +func GeomPrep_NewPreparedPoint(point Geom_Puntal) *GeomPrep_PreparedPoint { + base := GeomPrep_NewBasicPreparedGeometry(java.Cast[*Geom_Geometry](point.(java.Polymorphic))) + pp := &GeomPrep_PreparedPoint{GeomPrep_BasicPreparedGeometry: base} + base.child = pp + return pp +} + +func (p *GeomPrep_PreparedPoint) Intersects_BODY(g *Geom_Geometry) bool { + if !p.envelopesIntersect(g) { + return false + } + + return p.IsAnyTargetComponentInTest(g) +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon.go b/internal/jtsport/jts/geom_prep_prepared_polygon.go new file mode 100644 index 00000000..94374b4d --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon.go @@ -0,0 +1,96 @@ +package jts + +import ( + "sync" + + "github.com/peterstace/simplefeatures/internal/jtsport/java" +) + +type GeomPrep_PreparedPolygon struct { + *GeomPrep_BasicPreparedGeometry + isRectangle bool + segIntFinder *Noding_FastSegmentSetIntersectionFinder + pia AlgorithmLocate_PointOnGeometryLocator + // TRANSLITERATION NOTE: sync.Mutex replaces Java's synchronized keyword. + mu sync.Mutex +} + +func GeomPrep_NewPreparedPolygon(poly Geom_Polygonal) *GeomPrep_PreparedPolygon { + base := GeomPrep_NewBasicPreparedGeometry(java.Cast[*Geom_Geometry](poly.(java.Polymorphic))) + pp := &GeomPrep_PreparedPolygon{ + GeomPrep_BasicPreparedGeometry: base, + isRectangle: base.GetGeometry().IsRectangle(), + } + base.child = pp + return pp +} + +func (p *GeomPrep_PreparedPolygon) GetChild() java.Polymorphic { return nil } + +// TRANSLITERATION NOTE: sync.Mutex replaces Java's synchronized keyword. +func (p *GeomPrep_PreparedPolygon) GetIntersectionFinder() *Noding_FastSegmentSetIntersectionFinder { + p.mu.Lock() + defer p.mu.Unlock() + if p.segIntFinder == nil { + p.segIntFinder = Noding_NewFastSegmentSetIntersectionFinder(Noding_SegmentStringUtil_ExtractSegmentStrings(p.GetGeometry())) + } + return p.segIntFinder +} + +// TRANSLITERATION NOTE: sync.Mutex replaces Java's synchronized keyword. +func (p *GeomPrep_PreparedPolygon) GetPointLocator() AlgorithmLocate_PointOnGeometryLocator { + p.mu.Lock() + defer p.mu.Unlock() + if p.pia == nil { + p.pia = AlgorithmLocate_NewIndexedPointInAreaLocator(p.GetGeometry()) + } + return p.pia +} + +func (p *GeomPrep_PreparedPolygon) Intersects_BODY(g *Geom_Geometry) bool { + // envelope test + if !p.envelopesIntersect(g) { + return false + } + + // optimization for rectangles + if p.isRectangle { + return OperationPredicate_RectangleIntersects_Intersects(java.Cast[*Geom_Polygon](p.GetGeometry()), g) + } + + return GeomPrep_PreparedPolygonIntersects_Intersects(p, g) +} + +func (p *GeomPrep_PreparedPolygon) Contains_BODY(g *Geom_Geometry) bool { + // short-circuit test + if !p.envelopeCovers(g) { + return false + } + + // optimization for rectangles + if p.isRectangle { + return OperationPredicate_RectangleContains_Contains(java.Cast[*Geom_Polygon](p.GetGeometry()), g) + } + + return GeomPrep_PreparedPolygonContains_Contains(p, g) +} + +func (p *GeomPrep_PreparedPolygon) ContainsProperly_BODY(g *Geom_Geometry) bool { + // short-circuit test + if !p.envelopeCovers(g) { + return false + } + return GeomPrep_PreparedPolygonContainsProperly_ContainsProperly(p, g) +} + +func (p *GeomPrep_PreparedPolygon) Covers_BODY(g *Geom_Geometry) bool { + // short-circuit test + if !p.envelopeCovers(g) { + return false + } + // optimization for rectangle arguments + if p.isRectangle { + return true + } + return GeomPrep_PreparedPolygonCovers_Covers(p, g) +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_contains.go b/internal/jtsport/jts/geom_prep_prepared_polygon_contains.go new file mode 100644 index 00000000..dceb6879 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_contains.go @@ -0,0 +1,32 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedPolygonContains struct { + *GeomPrep_AbstractPreparedPolygonContains +} + +func (c *GeomPrep_PreparedPolygonContains) GetChild() java.Polymorphic { return nil } + +func GeomPrep_PreparedPolygonContains_Contains(prep *GeomPrep_PreparedPolygon, geom *Geom_Geometry) bool { + polyInt := geomPrep_NewPreparedPolygonContains(prep) + return polyInt.Contains(geom) +} + +func geomPrep_NewPreparedPolygonContains(prepPoly *GeomPrep_PreparedPolygon) *GeomPrep_PreparedPolygonContains { + base := geomPrep_NewAbstractPreparedPolygonContains(prepPoly) + c := &GeomPrep_PreparedPolygonContains{ + GeomPrep_AbstractPreparedPolygonContains: base, + } + base.child = c + return c +} + +func (c *GeomPrep_PreparedPolygonContains) Contains(geom *Geom_Geometry) bool { + return c.eval(geom) +} + +func (c *GeomPrep_PreparedPolygonContains) FullTopologicalPredicate_BODY(geom *Geom_Geometry) bool { + isContained := c.prepPoly.GetGeometry().Contains(geom) + return isContained +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_contains_properly.go b/internal/jtsport/jts/geom_prep_prepared_polygon_contains_properly.go new file mode 100644 index 00000000..0bbb2d31 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_contains_properly.go @@ -0,0 +1,47 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedPolygonContainsProperly struct { + *GeomPrep_PreparedPolygonPredicate +} + +func (c *GeomPrep_PreparedPolygonContainsProperly) GetChild() java.Polymorphic { return nil } +func (c *GeomPrep_PreparedPolygonContainsProperly) GetParent() java.Polymorphic { return nil } + +func GeomPrep_PreparedPolygonContainsProperly_ContainsProperly(prep *GeomPrep_PreparedPolygon, geom *Geom_Geometry) bool { + polyInt := geomPrep_NewPreparedPolygonContainsProperly(prep) + return polyInt.ContainsProperly(geom) +} + +func geomPrep_NewPreparedPolygonContainsProperly(prepPoly *GeomPrep_PreparedPolygon) *GeomPrep_PreparedPolygonContainsProperly { + base := geomPrep_NewPreparedPolygonPredicate(prepPoly) + c := &GeomPrep_PreparedPolygonContainsProperly{ + GeomPrep_PreparedPolygonPredicate: base, + } + base.child = c + return c +} + +func (c *GeomPrep_PreparedPolygonContainsProperly) ContainsProperly(geom *Geom_Geometry) bool { + isAllInPrepGeomAreaInterior := c.isAllTestComponentsInTargetInterior(geom) + if !isAllInPrepGeomAreaInterior { + return false + } + + lineSegStr := Noding_SegmentStringUtil_ExtractSegmentStrings(geom) + segsIntersect := c.prepPoly.GetIntersectionFinder().Intersects(lineSegStr) + if segsIntersect { + return false + } + + if java.InstanceOf[Geom_Polygonal](geom) { + // TODO: generalize this to handle GeometryCollections + isTargetGeomInTestArea := c.isAnyTargetComponentInAreaTest(geom, c.prepPoly.GetRepresentativePoints()) + if isTargetGeomInTestArea { + return false + } + } + + return true +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_covers.go b/internal/jtsport/jts/geom_prep_prepared_polygon_covers.go new file mode 100644 index 00000000..46b836d0 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_covers.go @@ -0,0 +1,33 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedPolygonCovers struct { + *GeomPrep_AbstractPreparedPolygonContains +} + +func (c *GeomPrep_PreparedPolygonCovers) GetChild() java.Polymorphic { return nil } + +func GeomPrep_PreparedPolygonCovers_Covers(prep *GeomPrep_PreparedPolygon, geom *Geom_Geometry) bool { + polyInt := geomPrep_NewPreparedPolygonCovers(prep) + return polyInt.Covers(geom) +} + +func geomPrep_NewPreparedPolygonCovers(prepPoly *GeomPrep_PreparedPolygon) *GeomPrep_PreparedPolygonCovers { + base := geomPrep_NewAbstractPreparedPolygonContains(prepPoly) + c := &GeomPrep_PreparedPolygonCovers{ + GeomPrep_AbstractPreparedPolygonContains: base, + } + base.requireSomePointInInterior = false + base.child = c + return c +} + +func (c *GeomPrep_PreparedPolygonCovers) Covers(geom *Geom_Geometry) bool { + return c.eval(geom) +} + +func (c *GeomPrep_PreparedPolygonCovers) FullTopologicalPredicate_BODY(geom *Geom_Geometry) bool { + result := c.prepPoly.GetGeometry().Covers(geom) + return result +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_intersects.go b/internal/jtsport/jts/geom_prep_prepared_polygon_intersects.go new file mode 100644 index 00000000..4288ac69 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_intersects.go @@ -0,0 +1,63 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedPolygonIntersects struct { + *GeomPrep_PreparedPolygonPredicate +} + +func (c *GeomPrep_PreparedPolygonIntersects) GetChild() java.Polymorphic { return nil } +func (c *GeomPrep_PreparedPolygonIntersects) GetParent() java.Polymorphic { return nil } + +func GeomPrep_PreparedPolygonIntersects_Intersects(prep *GeomPrep_PreparedPolygon, geom *Geom_Geometry) bool { + polyInt := geomPrep_NewPreparedPolygonIntersects(prep) + return polyInt.Intersects(geom) +} + +func geomPrep_NewPreparedPolygonIntersects(prepPoly *GeomPrep_PreparedPolygon) *GeomPrep_PreparedPolygonIntersects { + base := geomPrep_NewPreparedPolygonPredicate(prepPoly) + c := &GeomPrep_PreparedPolygonIntersects{ + GeomPrep_PreparedPolygonPredicate: base, + } + base.child = c + return c +} + +func (c *GeomPrep_PreparedPolygonIntersects) Intersects(geom *Geom_Geometry) bool { + // Do point-in-poly tests first, since they are cheaper and may result in a + // quick positive result. + // + // If a point of any test components lie in target, result is true + isInPrepGeomArea := c.isAnyTestComponentInTarget(geom) + if isInPrepGeomArea { + return true + } + // If input contains only points, then at + // this point it is known that none of them are contained in the target + if geom.GetDimension() == 0 { + return false + } + // If any segments intersect, result is true + lineSegStr := Noding_SegmentStringUtil_ExtractSegmentStrings(geom) + // only request intersection finder if there are segments + // (i.e. NOT for point inputs) + if len(lineSegStr) > 0 { + segsIntersect := c.prepPoly.GetIntersectionFinder().Intersects(lineSegStr) + if segsIntersect { + return true + } + } + + // If the test has dimension = 2 as well, it is necessary to test for proper + // inclusion of the target. Since no segments intersect, it is sufficient to + // test representative points. + if geom.GetDimension() == 2 { + // TODO: generalize this to handle GeometryCollections + isPrepGeomInArea := c.isAnyTargetComponentInAreaTest(geom, c.prepPoly.GetRepresentativePoints()) + if isPrepGeomInArea { + return true + } + } + + return false +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_intersects_stress_test.go b/internal/jtsport/jts/geom_prep_prepared_polygon_intersects_stress_test.go new file mode 100644 index 00000000..57123fe5 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_intersects_stress_test.go @@ -0,0 +1,116 @@ +package jts + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/peterstace/simplefeatures/internal/jtsport/java" +) + +const geomPrep_PreparedPolygonIntersectsStressTest_MAX_ITER = 10000 + +var geomPrep_preparedPolygonIntersectsStressTest_pm = Geom_NewPrecisionModel() +var geomPrep_preparedPolygonIntersectsStressTest_fact = Geom_NewGeometryFactoryWithPrecisionModelAndSRID(geomPrep_preparedPolygonIntersectsStressTest_pm, 0) +var geomPrep_preparedPolygonIntersectsStressTest_wktRdr = Io_NewWKTReaderWithFactory(geomPrep_preparedPolygonIntersectsStressTest_fact) +var geomPrep_preparedPolygonIntersectsStressTest_wktWriter = Io_NewWKTWriter() + +// TRANSLITERATION NOTE: Java main() method (JUnit TestRunner entry point) not +// ported - Go uses `go test`. + +type geomPrep_PreparedPolygonIntersectsStressTest struct { + testFailed bool +} + +// TRANSLITERATION NOTE: Java constructor +// PreparedPolygonIntersectsStressTest(String name) not ported - JUnit TestCase +// infrastructure not needed in Go. + +func TestPreparedPolygonIntersectsStress(t *testing.T) { + st := &geomPrep_PreparedPolygonIntersectsStressTest{} + st.test() +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) test() { + st.run(1000) +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) run(nPts int) { + // Geometry poly = createCircle(new Coordinate(0, 0), 100, nPts); + poly := st.createSineStar(Geom_NewCoordinateWithXY(0, 0), 100, nPts) + //System.out.println(poly); + + //System.out.println(); + //System.out.println("Running with " + nPts + " points"); + st.testWithGeometry(poly) +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) createCircle(origin *Geom_Coordinate, size float64, nPts int) *Geom_Geometry { + gsf := Util_NewGeometricShapeFactory() + gsf.SetCentre(origin) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + circle := gsf.CreateCircle() + // Polygon gRect = gsf.createRectangle(); + // Geometry g = gRect.getExteriorRing(); + return circle.Geom_Geometry +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) createSineStar(origin *Geom_Coordinate, size float64, nPts int) *Geom_Geometry { + gsf := GeomUtil_NewSineStarFactory() + gsf.SetCentre(origin) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + gsf.SetArmLengthRatio(0.1) + gsf.SetNumArms(20) + poly := gsf.CreateSineStar() + return poly +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) createTestLineFromEnvelope(env *Geom_Envelope, size float64, nPts int) *Geom_LineString { + width := env.GetWidth() + xOffset := width * rand.Float64() + yOffset := env.GetHeight() * rand.Float64() + basePt := Geom_NewCoordinateWithXY( + env.GetMinX()+xOffset, + env.GetMinY()+yOffset) + line := st.createTestLineFromCoordinate(basePt, size, nPts) + return line +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) createTestLineFromCoordinate(base *Geom_Coordinate, size float64, nPts int) *Geom_LineString { + gsf := Util_NewGeometricShapeFactory() + gsf.SetCentre(base) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + circle := gsf.CreateCircle() + // System.out.println(circle); + return java.Cast[*Geom_LineString](circle.Geom_Geometry.GetBoundary()) +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) testWithGeometry(g *Geom_Geometry) { + count := 0 + for count < geomPrep_PreparedPolygonIntersectsStressTest_MAX_ITER { + count++ + line := st.createTestLineFromEnvelope(g.GetEnvelopeInternal(), 10, 20) + + // System.out.println("Test # " + count); + // System.out.println(line); + st.testResultsEqual(g, line) + } +} + +func (st *geomPrep_PreparedPolygonIntersectsStressTest) testResultsEqual(g *Geom_Geometry, line *Geom_LineString) { + slowIntersects := g.Intersects(line.Geom_Geometry) + + pgFact := GeomPrep_NewPreparedGeometryFactory() + prepGeom := pgFact.Create(g) + + fastIntersects := prepGeom.Intersects(line.Geom_Geometry) + + if slowIntersects != fastIntersects { + fmt.Println(line) + fmt.Printf("Slow = %v, Fast = %v\n", slowIntersects, fastIntersects) + panic("Different results found for intersects() !") + } +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_predicate.go b/internal/jtsport/jts/geom_prep_prepared_polygon_predicate.go new file mode 100644 index 00000000..a4230e0f --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_predicate.go @@ -0,0 +1,86 @@ +package jts + +import "github.com/peterstace/simplefeatures/internal/jtsport/java" + +type GeomPrep_PreparedPolygonPredicate struct { + child java.Polymorphic + prepPoly *GeomPrep_PreparedPolygon + targetPointLocator AlgorithmLocate_PointOnGeometryLocator +} + +func (p *GeomPrep_PreparedPolygonPredicate) GetChild() java.Polymorphic { return p.child } + +func geomPrep_NewPreparedPolygonPredicate(prepPoly *GeomPrep_PreparedPolygon) *GeomPrep_PreparedPolygonPredicate { + return &GeomPrep_PreparedPolygonPredicate{ + prepPoly: prepPoly, + targetPointLocator: prepPoly.GetPointLocator(), + } +} + +func (p *GeomPrep_PreparedPolygonPredicate) isAllTestComponentsInTarget(testGeom *Geom_Geometry) bool { + coords := GeomUtil_ComponentCoordinateExtracter_GetCoordinates(testGeom) + for _, c := range coords { + loc := p.targetPointLocator.Locate(c) + if loc == Geom_Location_Exterior { + return false + } + } + return true +} + +func (p *GeomPrep_PreparedPolygonPredicate) isAllTestComponentsInTargetInterior(testGeom *Geom_Geometry) bool { + coords := GeomUtil_ComponentCoordinateExtracter_GetCoordinates(testGeom) + for _, c := range coords { + loc := p.targetPointLocator.Locate(c) + if loc != Geom_Location_Interior { + return false + } + } + return true +} + +func (p *GeomPrep_PreparedPolygonPredicate) isAnyTestComponentInTarget(testGeom *Geom_Geometry) bool { + coords := GeomUtil_ComponentCoordinateExtracter_GetCoordinates(testGeom) + for _, c := range coords { + loc := p.targetPointLocator.Locate(c) + if loc != Geom_Location_Exterior { + return true + } + } + return false +} + +func (p *GeomPrep_PreparedPolygonPredicate) isAllTestPointsInTarget(testGeom *Geom_Geometry) bool { + for i := 0; i < testGeom.GetNumGeometries(); i++ { + pt := java.Cast[*Geom_Point](testGeom.GetGeometryN(i)) + c := pt.GetCoordinate() + loc := p.targetPointLocator.Locate(c) + if loc == Geom_Location_Exterior { + return false + } + } + return true +} + +func (p *GeomPrep_PreparedPolygonPredicate) isAnyTestPointInTargetInterior(testGeom *Geom_Geometry) bool { + for i := 0; i < testGeom.GetNumGeometries(); i++ { + pt := java.Cast[*Geom_Point](testGeom.GetGeometryN(i)) + c := pt.GetCoordinate() + loc := p.targetPointLocator.Locate(c) + if loc == Geom_Location_Interior { + return true + } + } + return false +} + +func (p *GeomPrep_PreparedPolygonPredicate) isAnyTargetComponentInAreaTest(testGeom *Geom_Geometry, targetRepPts []*Geom_Coordinate) bool { + piaLoc := AlgorithmLocate_NewSimplePointInAreaLocator(testGeom) + for _, c := range targetRepPts { + loc := piaLoc.Locate(c) + if loc != Geom_Location_Exterior { + return true + } + } + return false +} diff --git a/internal/jtsport/jts/geom_prep_prepared_polygon_predicate_stress_test.go b/internal/jtsport/jts/geom_prep_prepared_polygon_predicate_stress_test.go new file mode 100644 index 00000000..9edb1372 --- /dev/null +++ b/internal/jtsport/jts/geom_prep_prepared_polygon_predicate_stress_test.go @@ -0,0 +1,90 @@ +package jts + +import ( + "testing" + + "github.com/peterstace/simplefeatures/internal/jtsport/java" +) + +// TRANSLITERATION NOTE: Java main() method (JUnit TestRunner entry point) not +// ported - Go uses `go test`. + +type geomPrep_PreparedPolygonPredicateStressTest struct { + testFailed bool +} + +// TRANSLITERATION NOTE: Java constructor +// PreparedPolygonPredicateStressTest(String name) not ported - JUnit TestCase +// infrastructure not needed in Go. + +func TestPreparedPolygonPredicateStress(t *testing.T) { + st := &geomPrep_PreparedPolygonPredicateStressTest{} + st.test() +} + +func (st *geomPrep_PreparedPolygonPredicateStressTest) test() { + tester := geomPrep_newPreparedPolygonPredicateStressTest_PredicateStressTester(st) + tester.Run(1000) +} + +type geomPrep_PreparedPolygonPredicateStressTest_PredicateStressTester struct { + *GeomPrep_StressTestHarness + child java.Polymorphic + outer *geomPrep_PreparedPolygonPredicateStressTest +} + +func (p *geomPrep_PreparedPolygonPredicateStressTest_PredicateStressTester) GetChild() java.Polymorphic { + return p.child +} + +func (p *geomPrep_PreparedPolygonPredicateStressTest_PredicateStressTester) GetParent() java.Polymorphic { + return p.GeomPrep_StressTestHarness +} + +func geomPrep_newPreparedPolygonPredicateStressTest_PredicateStressTester(outer *geomPrep_PreparedPolygonPredicateStressTest) *geomPrep_PreparedPolygonPredicateStressTest_PredicateStressTester { + h := GeomPrep_NewStressTestHarness() + pt := &geomPrep_PreparedPolygonPredicateStressTest_PredicateStressTester{ + GeomPrep_StressTestHarness: h, + outer: outer, + } + h.child = pt + return pt +} + +func (p *geomPrep_PreparedPolygonPredicateStressTest_PredicateStressTester) CheckResult_BODY(target, test *Geom_Geometry) bool { + if !p.outer.checkIntersects(target, test) { + return false + } + if !p.outer.checkContains(target, test) { + return false + } + return true +} + +func (st *geomPrep_PreparedPolygonPredicateStressTest) checkContains(target, test *Geom_Geometry) bool { + expectedResult := target.Contains(test) + + pgFact := GeomPrep_NewPreparedGeometryFactory() + prepGeom := pgFact.Create(target) + + prepResult := prepGeom.Contains(test) + + if prepResult != expectedResult { + return false + } + return true +} + +func (st *geomPrep_PreparedPolygonPredicateStressTest) checkIntersects(target, test *Geom_Geometry) bool { + expectedResult := target.Intersects(test) + + pgFact := GeomPrep_NewPreparedGeometryFactory() + prepGeom := pgFact.Create(target) + + prepResult := prepGeom.Intersects(test) + + if prepResult != expectedResult { + return false + } + return true +} diff --git a/internal/jtsport/jts/geom_prep_stress_test_harness.go b/internal/jtsport/jts/geom_prep_stress_test_harness.go new file mode 100644 index 00000000..9b01f8ae --- /dev/null +++ b/internal/jtsport/jts/geom_prep_stress_test_harness.go @@ -0,0 +1,114 @@ +package jts + +import ( + "math/rand" + + "github.com/peterstace/simplefeatures/internal/jtsport/java" +) + +const geomPrep_StressTestHarness_MAX_ITER = 10000 + +var geomPrep_stressTestHarness_pm = Geom_NewPrecisionModel() +var geomPrep_stressTestHarness_fact = Geom_NewGeometryFactoryWithPrecisionModelAndSRID(geomPrep_stressTestHarness_pm, 0) +var geomPrep_stressTestHarness_wktRdr = Io_NewWKTReaderWithFactory(geomPrep_stressTestHarness_fact) +var geomPrep_stressTestHarness_wktWriter = Io_NewWKTWriter() + +type GeomPrep_StressTestHarness struct { + child java.Polymorphic + numTargetPts int +} + +func (h *GeomPrep_StressTestHarness) GetChild() java.Polymorphic { return h.child } +func (h *GeomPrep_StressTestHarness) GetParent() java.Polymorphic { return nil } + +func GeomPrep_NewStressTestHarness() *GeomPrep_StressTestHarness { + return &GeomPrep_StressTestHarness{ + numTargetPts: 1000, + } +} + +func (h *GeomPrep_StressTestHarness) SetTargetSize(nPts int) { + h.numTargetPts = nPts +} + +func (h *GeomPrep_StressTestHarness) Run(nIter int) { + //System.out.println("Running " + nIter + " tests"); + // Geometry poly = createCircle(new Coordinate(0, 0), 100, nPts); + poly := h.createSineStar(Geom_NewCoordinateWithXY(0, 0), 100, h.numTargetPts) + //System.out.println(poly); + + //System.out.println(); + //System.out.println("Running with " + nPts + " points"); + h.RunWithTarget(nIter, poly) +} + +func (h *GeomPrep_StressTestHarness) createCircle(origin *Geom_Coordinate, size float64, nPts int) *Geom_Geometry { + gsf := Util_NewGeometricShapeFactory() + gsf.SetCentre(origin) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + circle := gsf.CreateCircle() + // Polygon gRect = gsf.createRectangle(); + // Geometry g = gRect.getExteriorRing(); + return circle.Geom_Geometry +} + +func (h *GeomPrep_StressTestHarness) createSineStar(origin *Geom_Coordinate, size float64, nPts int) *Geom_Geometry { + gsf := GeomUtil_NewSineStarFactory() + gsf.SetCentre(origin) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + gsf.SetArmLengthRatio(0.1) + gsf.SetNumArms(20) + poly := gsf.CreateSineStar() + return poly +} + +func (h *GeomPrep_StressTestHarness) createRandomTestGeometry(env *Geom_Envelope, size float64, nPts int) *Geom_Geometry { + width := env.GetWidth() + xOffset := width * rand.Float64() + yOffset := env.GetHeight() * rand.Float64() + basePt := Geom_NewCoordinateWithXY( + env.GetMinX()+xOffset, + env.GetMinY()+yOffset) + test := h.createTestCircle(basePt, size, nPts) + if java.InstanceOf[*Geom_Polygon](test) && rand.Float64() > 0.5 { + test = test.GetBoundary() + } + return test +} + +func (h *GeomPrep_StressTestHarness) createTestCircle(base *Geom_Coordinate, size float64, nPts int) *Geom_Geometry { + gsf := Util_NewGeometricShapeFactory() + gsf.SetCentre(base) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + circle := gsf.CreateCircle() + // System.out.println(circle); + return circle.Geom_Geometry +} + +func (h *GeomPrep_StressTestHarness) RunWithTarget(nIter int, target *Geom_Geometry) { + count := 0 + for count < nIter { + count++ + test := h.createRandomTestGeometry(target.GetEnvelopeInternal(), 10, 20) + + // System.out.println("Test # " + count); + // System.out.println(line); + // System.out.println("Test[" + count + "] " + target.getClass() + "/" + test.getClass()); + isResultCorrect := h.CheckResult(target, test) + if !isResultCorrect { + panic("Invalid result found") + } + } +} + +func (h *GeomPrep_StressTestHarness) CheckResult(target, test *Geom_Geometry) bool { + if impl, ok := java.GetLeaf(h).(interface { + CheckResult_BODY(*Geom_Geometry, *Geom_Geometry) bool + }); ok { + return impl.CheckResult_BODY(target, test) + } + panic("abstract method called") +} diff --git a/internal/jtsport/jts/geom_util_sine_star_factory.go b/internal/jtsport/jts/geom_util_sine_star_factory.go new file mode 100644 index 00000000..18aa4101 --- /dev/null +++ b/internal/jtsport/jts/geom_util_sine_star_factory.go @@ -0,0 +1,88 @@ +package jts + +import "math" + +func GeomUtil_SineStarFactory_Create(origin *Geom_Coordinate, size float64, nPts int, nArms int, armLengthRatio float64) *Geom_Geometry { + gsf := GeomUtil_NewSineStarFactory() + gsf.SetCentre(origin) + gsf.SetSize(size) + gsf.SetNumPoints(nPts) + gsf.SetArmLengthRatio(armLengthRatio) + gsf.SetNumArms(nArms) + poly := gsf.CreateSineStar() + return poly +} + +type GeomUtil_SineStarFactory struct { + *Util_GeometricShapeFactory + numArms int + armLengthRatio float64 +} + +func GeomUtil_NewSineStarFactory() *GeomUtil_SineStarFactory { + return GeomUtil_NewSineStarFactoryWithFactory(Geom_NewGeometryFactoryDefault()) +} + +func GeomUtil_NewSineStarFactoryWithFactory(geomFact *Geom_GeometryFactory) *GeomUtil_SineStarFactory { + return &GeomUtil_SineStarFactory{ + Util_GeometricShapeFactory: Util_NewGeometricShapeFactoryWithFactory(geomFact), + numArms: 8, + armLengthRatio: 0.5, + } +} + +func (gsf *GeomUtil_SineStarFactory) SetNumArms(numArms int) { + gsf.numArms = numArms +} + +func (gsf *GeomUtil_SineStarFactory) SetArmLengthRatio(armLengthRatio float64) { + gsf.armLengthRatio = armLengthRatio +} + +func (gsf *GeomUtil_SineStarFactory) CreateSineStar() *Geom_Geometry { + env := gsf.dim.getEnvelope() + radius := env.GetWidth() / 2.0 + + armRatio := gsf.armLengthRatio + if armRatio < 0.0 { + armRatio = 0.0 + } + if armRatio > 1.0 { + armRatio = 1.0 + } + + armMaxLen := armRatio * radius + insideRadius := (1 - armRatio) * radius + + centreX := env.GetMinX() + radius + centreY := env.GetMinY() + radius + + pts := make([]*Geom_Coordinate, gsf.nPts+1) + iPt := 0 + for i := 0; i < gsf.nPts; i++ { + // the fraction of the way through the current arm - in [0,1] + ptArcFrac := (float64(i) / float64(gsf.nPts)) * float64(gsf.numArms) + armAngFrac := ptArcFrac - math.Floor(ptArcFrac) + + // the angle for the current arm - in [0,2Pi] + // (each arm is a complete sine wave cycle) + armAng := 2 * math.Pi * armAngFrac + // the current length of the arm + armLenFrac := (math.Cos(armAng) + 1.0) / 2.0 + + // the current radius of the curve (core + arm) + curveRadius := insideRadius + armMaxLen*armLenFrac + + // the current angle of the curve + ang := float64(i) * (2 * math.Pi / float64(gsf.nPts)) + x := curveRadius*math.Cos(ang) + centreX + y := curveRadius*math.Sin(ang) + centreY + pts[iPt] = gsf.coord(x, y) + iPt++ + } + pts[iPt] = Geom_NewCoordinateFromCoordinate(pts[0]) + + ring := gsf.geomFact.CreateLinearRingFromCoordinates(pts) + poly := gsf.geomFact.CreatePolygonFromLinearRing(ring) + return poly.Geom_Geometry +} diff --git a/internal/jtsport/jts/jtstest_geomop_prepared_geometry_operation.go b/internal/jtsport/jts/jtstest_geomop_prepared_geometry_operation.go new file mode 100644 index 00000000..91304864 --- /dev/null +++ b/internal/jtsport/jts/jtstest_geomop_prepared_geometry_operation.go @@ -0,0 +1,90 @@ +package jts + +var _ JtstestGeomop_GeometryOperation = (*JtstestGeomop_PreparedGeometryOperation)(nil) + +type JtstestGeomop_PreparedGeometryOperation struct { + chainOp *JtstestGeomop_GeometryMethodOperation +} + +func JtstestGeomop_NewPreparedGeometryOperation() *JtstestGeomop_PreparedGeometryOperation { + return &JtstestGeomop_PreparedGeometryOperation{ + chainOp: JtstestGeomop_NewGeometryMethodOperation(), + } +} + +func (op *JtstestGeomop_PreparedGeometryOperation) IsJtstestGeomop_GeometryOperation() {} + +func (op *JtstestGeomop_PreparedGeometryOperation) GetReturnType(opName string) string { + if jtstestGeomop_PreparedGeometryOperation_isPreparedOp(opName) { + return "boolean" + } + return op.chainOp.GetReturnType(opName) +} + +func JtstestGeomop_NewPreparedGeometryOperationWithChainOp(chainOp *JtstestGeomop_GeometryMethodOperation) *JtstestGeomop_PreparedGeometryOperation { + return &JtstestGeomop_PreparedGeometryOperation{ + chainOp: chainOp, + } +} + +func jtstestGeomop_PreparedGeometryOperation_isPreparedOp(opName string) bool { + if opName == "intersects" { + return true + } + if opName == "contains" { + return true + } + if opName == "containsProperly" { + return true + } + if opName == "covers" { + return true + } + return false +} + +func (op *JtstestGeomop_PreparedGeometryOperation) Invoke(opName string, geometry *Geom_Geometry, args []any) (JtstestTestrunner_Result, error) { + if !jtstestGeomop_PreparedGeometryOperation_isPreparedOp(opName) { + return op.chainOp.Invoke(opName, geometry, args) + } + return op.invokePreparedOp(opName, geometry, args), nil +} + +func (op *JtstestGeomop_PreparedGeometryOperation) invokePreparedOp(opName string, geometry *Geom_Geometry, args []any) JtstestTestrunner_Result { + g2 := args[0].(*Geom_Geometry) + if opName == "intersects" { + return JtstestTestrunner_NewBooleanResult(jtstestGeomop_PreparedGeometryOp_intersects(geometry, g2)) + } + if opName == "contains" { + return JtstestTestrunner_NewBooleanResult(jtstestGeomop_PreparedGeometryOp_contains(geometry, g2)) + } + if opName == "containsProperly" { + return JtstestTestrunner_NewBooleanResult(jtstestGeomop_PreparedGeometryOp_containsProperly(geometry, g2)) + } + if opName == "covers" { + return JtstestTestrunner_NewBooleanResult(jtstestGeomop_PreparedGeometryOp_covers(geometry, g2)) + } + return nil +} + +// Inner class PreparedGeometryOp + +func jtstestGeomop_PreparedGeometryOp_intersects(g1 *Geom_Geometry, g2 *Geom_Geometry) bool { + prepGeom := GeomPrep_PreparedGeometryFactory_Prepare(g1) + return prepGeom.Intersects(g2) +} + +func jtstestGeomop_PreparedGeometryOp_contains(g1 *Geom_Geometry, g2 *Geom_Geometry) bool { + prepGeom := GeomPrep_PreparedGeometryFactory_Prepare(g1) + return prepGeom.Contains(g2) +} + +func jtstestGeomop_PreparedGeometryOp_containsProperly(g1 *Geom_Geometry, g2 *Geom_Geometry) bool { + prepGeom := GeomPrep_PreparedGeometryFactory_Prepare(g1) + return prepGeom.ContainsProperly(g2) +} + +func jtstestGeomop_PreparedGeometryOp_covers(g1 *Geom_Geometry, g2 *Geom_Geometry) bool { + prepGeom := GeomPrep_PreparedGeometryFactory_Prepare(g1) + return prepGeom.Covers(g2) +} diff --git a/internal/jtsport/jts/jtstest_testrunner_test_reader.go b/internal/jtsport/jts/jtstest_testrunner_test_reader.go index e8b3649e..cc4368be 100644 --- a/internal/jtsport/jts/jtstest_testrunner_test_reader.go +++ b/internal/jtsport/jts/jtstest_testrunner_test_reader.go @@ -238,7 +238,11 @@ func (r *JtstestTestrunner_TestReader) parseResultMatcher(runElement *xmlRun) Jt func (r *JtstestTestrunner_TestReader) getInstance(classname string, baseClass string) any { // TRANSLITERATION NOTE: Go doesn't support dynamic class loading like Java's reflection. - // This would need to be extended with explicit type mapping if custom operations are needed. + // Instead, we use explicit type mapping for known classes. + switch classname { + case "org.locationtech.jtstest.geomop.PreparedGeometryOperation": + return JtstestGeomop_NewPreparedGeometryOperation() + } return nil } diff --git a/internal/jtsport/jts/noding_fast_segment_set_intersection_finder.go b/internal/jtsport/jts/noding_fast_segment_set_intersection_finder.go new file mode 100644 index 00000000..4f36370c --- /dev/null +++ b/internal/jtsport/jts/noding_fast_segment_set_intersection_finder.go @@ -0,0 +1,39 @@ +package jts + +// Noding_FastSegmentSetIntersectionFinder finds if two sets of SegmentStrings +// intersect. Uses indexing for fast performance and to optimize repeated tests +// against a target set of lines. Short-circuited to return as soon an +// intersection is found. +// +// Immutable and thread-safe. +type Noding_FastSegmentSetIntersectionFinder struct { + segSetMutInt Noding_SegmentSetMutualIntersector +} + +// Noding_NewFastSegmentSetIntersectionFinder creates an intersection finder +// against a given set of segment strings. +func Noding_NewFastSegmentSetIntersectionFinder(baseSegStrings []Noding_SegmentString) *Noding_FastSegmentSetIntersectionFinder { + return &Noding_FastSegmentSetIntersectionFinder{ + segSetMutInt: Noding_NewMCIndexSegmentSetMutualIntersector(baseSegStrings), + } +} + +// GetSegmentSetIntersector gets the segment set intersector used by this class. +// This allows other uses of the same underlying indexed structure. +func (f *Noding_FastSegmentSetIntersectionFinder) GetSegmentSetIntersector() Noding_SegmentSetMutualIntersector { + return f.segSetMutInt +} + +// Intersects tests for intersections with a given set of target +// SegmentStrings. +func (f *Noding_FastSegmentSetIntersectionFinder) Intersects(segStrings []Noding_SegmentString) bool { + intFinder := Noding_NewSegmentIntersectionDetector() + return f.IntersectsWithDetector(segStrings, intFinder) +} + +// IntersectsWithDetector tests for intersections with a given set of target +// SegmentStrings using a given SegmentIntersectionDetector. +func (f *Noding_FastSegmentSetIntersectionFinder) IntersectsWithDetector(segStrings []Noding_SegmentString, intDetector *Noding_SegmentIntersectionDetector) bool { + f.segSetMutInt.Process(segStrings, intDetector) + return intDetector.HasIntersection() +} diff --git a/internal/jtsport/jts/noding_segment_intersection_detector.go b/internal/jtsport/jts/noding_segment_intersection_detector.go new file mode 100644 index 00000000..bdb83c9a --- /dev/null +++ b/internal/jtsport/jts/noding_segment_intersection_detector.go @@ -0,0 +1,151 @@ +package jts + +var _ Noding_SegmentIntersector = (*Noding_SegmentIntersectionDetector)(nil) + +// Noding_SegmentIntersectionDetector detects and records an intersection +// between two SegmentStrings, if one exists. Only a single intersection is +// recorded. This strategy can be configured to search for proper +// intersections. In this case, the presence of any kind of intersection will +// still be recorded, but searching will continue until either a proper +// intersection has been found or no intersections are detected. +type Noding_SegmentIntersectionDetector struct { + li *Algorithm_LineIntersector + findProper bool + findAllTypes bool + + hasIntersection bool + hasProperIntersection bool + hasNonProperIntersection bool + + intPt *Geom_Coordinate + intSegments []*Geom_Coordinate +} + +// IsNoding_SegmentIntersector is a marker method for interface identification. +func (sid *Noding_SegmentIntersectionDetector) IsNoding_SegmentIntersector() {} + +// Noding_NewSegmentIntersectionDetector creates an intersection finder using a +// RobustLineIntersector. +func Noding_NewSegmentIntersectionDetector() *Noding_SegmentIntersectionDetector { + return Noding_NewSegmentIntersectionDetectorWithLI(Algorithm_NewRobustLineIntersector().Algorithm_LineIntersector) +} + +// Noding_NewSegmentIntersectionDetectorWithLI creates an intersection finder +// using a given LineIntersector. +func Noding_NewSegmentIntersectionDetectorWithLI(li *Algorithm_LineIntersector) *Noding_SegmentIntersectionDetector { + return &Noding_SegmentIntersectionDetector{ + li: li, + } +} + +// SetFindProper sets whether processing must continue until a proper +// intersection is found. +func (sid *Noding_SegmentIntersectionDetector) SetFindProper(findProper bool) { + sid.findProper = findProper +} + +// SetFindAllIntersectionTypes sets whether processing can terminate once any +// intersection is found. +func (sid *Noding_SegmentIntersectionDetector) SetFindAllIntersectionTypes(findAllTypes bool) { + sid.findAllTypes = findAllTypes +} + +// HasIntersection tests whether an intersection was found. +func (sid *Noding_SegmentIntersectionDetector) HasIntersection() bool { + return sid.hasIntersection +} + +// HasProperIntersection tests whether a proper intersection was found. +func (sid *Noding_SegmentIntersectionDetector) HasProperIntersection() bool { + return sid.hasProperIntersection +} + +// HasNonProperIntersection tests whether a non-proper intersection was found. +func (sid *Noding_SegmentIntersectionDetector) HasNonProperIntersection() bool { + return sid.hasNonProperIntersection +} + +// GetIntersection gets the computed location of the intersection. Due to +// round-off, the location may not be exact. +func (sid *Noding_SegmentIntersectionDetector) GetIntersection() *Geom_Coordinate { + return sid.intPt +} + +// GetIntersectionSegments gets the endpoints of the intersecting segments. +func (sid *Noding_SegmentIntersectionDetector) GetIntersectionSegments() []*Geom_Coordinate { + return sid.intSegments +} + +// ProcessIntersections is called by clients of the SegmentIntersector class to +// process intersections for two segments of the SegmentStrings being +// intersected. Note that some clients (such as MonotoneChains) may optimize +// away this call for segment pairs which they have determined do not intersect +// (e.g. by a disjoint envelope test). +func (sid *Noding_SegmentIntersectionDetector) ProcessIntersections( + e0 Noding_SegmentString, segIndex0 int, + e1 Noding_SegmentString, segIndex1 int, +) { + // don't bother intersecting a segment with itself + if e0 == e1 && segIndex0 == segIndex1 { + return + } + + p00 := e0.GetCoordinate(segIndex0) + p01 := e0.GetCoordinate(segIndex0 + 1) + p10 := e1.GetCoordinate(segIndex1) + p11 := e1.GetCoordinate(segIndex1 + 1) + + sid.li.ComputeIntersection(p00, p01, p10, p11) + + if sid.li.HasIntersection() { + + // record intersection info + sid.hasIntersection = true + + isProper := sid.li.IsProper() + if isProper { + sid.hasProperIntersection = true + } + if !isProper { + sid.hasNonProperIntersection = true + } + + // If this is the kind of intersection we are searching for + // OR no location has yet been recorded + // save the location data + saveLocation := true + if sid.findProper && !isProper { + saveLocation = false + } + + if sid.intPt == nil || saveLocation { + + // record intersection location (approximate) + sid.intPt = sid.li.GetIntersection(0) + + // record intersecting segments + sid.intSegments = make([]*Geom_Coordinate, 4) + sid.intSegments[0] = p00 + sid.intSegments[1] = p01 + sid.intSegments[2] = p10 + sid.intSegments[3] = p11 + } + } +} + +// IsDone tests whether processing can terminate, because all required +// information has been obtained (e.g. an intersection of the desired type has +// been detected). +func (sid *Noding_SegmentIntersectionDetector) IsDone() bool { + // If finding all types, we can stop + // when both possible types have been found. + if sid.findAllTypes { + return sid.hasProperIntersection && sid.hasNonProperIntersection + } + + // If searching for a proper intersection, only stop if one is found + if sid.findProper { + return sid.hasProperIntersection + } + return sid.hasIntersection +} diff --git a/internal/jtsport/jts/noding_segment_string_util.go b/internal/jtsport/jts/noding_segment_string_util.go new file mode 100644 index 00000000..ff5b60a7 --- /dev/null +++ b/internal/jtsport/jts/noding_segment_string_util.go @@ -0,0 +1,70 @@ +package jts + +import ( + "fmt" + "strings" +) + +// Noding_SegmentStringUtil provides utility methods for processing SegmentStrings. +type Noding_SegmentStringUtil struct{} + +// Noding_SegmentStringUtil_ExtractSegmentStrings extracts all linear components +// from a given Geometry to SegmentStrings. The SegmentString data item is set +// to be the source Geometry. +func Noding_SegmentStringUtil_ExtractSegmentStrings(geom *Geom_Geometry) []Noding_SegmentString { + return Noding_SegmentStringUtil_ExtractNodedSegmentStrings(geom) +} + +// Noding_SegmentStringUtil_ExtractNodedSegmentStrings extracts all linear +// components from a given Geometry to NodedSegmentStrings. The SegmentString +// data item is set to be the source Geometry. +func Noding_SegmentStringUtil_ExtractNodedSegmentStrings(geom *Geom_Geometry) []Noding_SegmentString { + var segStr []Noding_SegmentString + lines := GeomUtil_LinearComponentExtracter_GetLines(geom) + for _, line := range lines { + pts := line.GetCoordinates() + segStr = append(segStr, Noding_NewNodedSegmentString(pts, geom)) + } + return segStr +} + +// Noding_SegmentStringUtil_ExtractBasicSegmentStrings extracts all linear +// components from a given Geometry to BasicSegmentStrings. The SegmentString +// data item is set to be the source Geometry. +func Noding_SegmentStringUtil_ExtractBasicSegmentStrings(geom *Geom_Geometry) []Noding_SegmentString { + var segStr []Noding_SegmentString + lines := GeomUtil_LinearComponentExtracter_GetLines(geom) + for _, line := range lines { + pts := line.GetCoordinates() + segStr = append(segStr, Noding_NewBasicSegmentString(pts, geom)) + } + return segStr +} + +// Noding_SegmentStringUtil_ToGeometry converts a collection of SegmentStrings +// into a Geometry. The geometry will be either a LineString or a +// MultiLineString (possibly empty). +func Noding_SegmentStringUtil_ToGeometry(segStrings []Noding_SegmentString, geomFact *Geom_GeometryFactory) *Geom_Geometry { + lines := make([]*Geom_LineString, len(segStrings)) + index := 0 + for _, ss := range segStrings { + line := geomFact.CreateLineStringFromCoordinates(ss.GetCoordinates()) + lines[index] = line + index++ + } + if len(lines) == 1 { + return lines[0].Geom_Geometry + } + return geomFact.CreateMultiLineStringFromLineStrings(lines).Geom_Geometry +} + +// Noding_SegmentStringUtil_ToString returns a string representation of a list +// of SegmentStrings. +func Noding_SegmentStringUtil_ToString(segStrings []Noding_SegmentString) string { + var buf strings.Builder + for _, segStr := range segStrings { + buf.WriteString(fmt.Sprintf("%v", segStr)) + buf.WriteString("\n") + } + return buf.String() +} diff --git a/internal/jtsport/jts/stubs.go b/internal/jtsport/jts/stubs.go index 97cc2aba..b7dd5238 100644 --- a/internal/jtsport/jts/stubs.go +++ b/internal/jtsport/jts/stubs.go @@ -268,3 +268,15 @@ func Simplify_TopologyPreservingSimplifier_Simplify(g *Geom_Geometry, distance f func Precision_GeometryPrecisionReducer_Reduce(g *Geom_Geometry, pm *Geom_PrecisionModel) *Geom_Geometry { panic("precision/GeometryPrecisionReducer not yet ported") } + +// ============================================================================= +// STUB: geom/util/AffineTransformation +// ============================================================================= + +// STUB: GeomUtil_AffineTransformation - geom/util/AffineTransformation not yet ported. +type GeomUtil_AffineTransformation struct{} + +// STUB: GeomUtil_AffineTransformation_RotationInstance - geom/util/AffineTransformation not yet ported. +func GeomUtil_AffineTransformation_RotationInstance(theta, x, y float64) Geom_CoordinateSequenceFilter { + panic("geom/util/AffineTransformation not yet ported") +} diff --git a/internal/jtsport/jts/util_geometric_shape_factory.go b/internal/jtsport/jts/util_geometric_shape_factory.go new file mode 100644 index 00000000..85b7aa81 --- /dev/null +++ b/internal/jtsport/jts/util_geometric_shape_factory.go @@ -0,0 +1,318 @@ +package jts + +import ( + "math" + + "github.com/peterstace/simplefeatures/internal/jtsport/java" +) + +type Util_GeometricShapeFactory struct { + geomFact *Geom_GeometryFactory + precModel *Geom_PrecisionModel + dim *util_GeometricShapeFactory_Dimensions + nPts int + rotationAngle float64 +} + +func Util_NewGeometricShapeFactory() *Util_GeometricShapeFactory { + return Util_NewGeometricShapeFactoryWithFactory(Geom_NewGeometryFactoryDefault()) +} + +func Util_NewGeometricShapeFactoryWithFactory(geomFact *Geom_GeometryFactory) *Util_GeometricShapeFactory { + return &Util_GeometricShapeFactory{ + geomFact: geomFact, + precModel: geomFact.GetPrecisionModel(), + dim: util_newGeometricShapeFactory_Dimensions(), + nPts: 100, + rotationAngle: 0.0, + } +} + +func (gsf *Util_GeometricShapeFactory) SetEnvelope(env *Geom_Envelope) { + gsf.dim.setEnvelope(env) +} + +func (gsf *Util_GeometricShapeFactory) SetBase(base *Geom_Coordinate) { + gsf.dim.setBase(base) +} + +func (gsf *Util_GeometricShapeFactory) SetCentre(centre *Geom_Coordinate) { + gsf.dim.setCentre(centre) +} + +func (gsf *Util_GeometricShapeFactory) SetNumPoints(nPts int) { gsf.nPts = nPts } + +func (gsf *Util_GeometricShapeFactory) SetSize(size float64) { gsf.dim.setSize(size) } + +func (gsf *Util_GeometricShapeFactory) SetWidth(width float64) { gsf.dim.setWidth(width) } + +func (gsf *Util_GeometricShapeFactory) SetHeight(height float64) { gsf.dim.setHeight(height) } + +func (gsf *Util_GeometricShapeFactory) SetRotation(radians float64) { + gsf.rotationAngle = radians +} + +func (gsf *Util_GeometricShapeFactory) rotate(geom *Geom_Geometry) *Geom_Geometry { + if gsf.rotationAngle != 0.0 { + trans := GeomUtil_AffineTransformation_RotationInstance(gsf.rotationAngle, + gsf.dim.getCentre().X, gsf.dim.getCentre().Y) + geom.ApplyCoordinateSequenceFilter(trans) + } + return geom +} + +func (gsf *Util_GeometricShapeFactory) CreateRectangle() *Geom_Polygon { + var i int + ipt := 0 + nSide := gsf.nPts / 4 + if nSide < 1 { + nSide = 1 + } + xSegLen := gsf.dim.getEnvelope().GetWidth() / float64(nSide) + ySegLen := gsf.dim.getEnvelope().GetHeight() / float64(nSide) + + pts := make([]*Geom_Coordinate, 4*nSide+1) + env := gsf.dim.getEnvelope() + + //double maxx = env.getMinX() + nSide * XsegLen; + //double maxy = env.getMinY() + nSide * XsegLen; + + for i = 0; i < nSide; i++ { + x := env.GetMinX() + float64(i)*xSegLen + y := env.GetMinY() + pts[ipt] = gsf.coord(x, y) + ipt++ + } + for i = 0; i < nSide; i++ { + x := env.GetMaxX() + y := env.GetMinY() + float64(i)*ySegLen + pts[ipt] = gsf.coord(x, y) + ipt++ + } + for i = 0; i < nSide; i++ { + x := env.GetMaxX() - float64(i)*xSegLen + y := env.GetMaxY() + pts[ipt] = gsf.coord(x, y) + ipt++ + } + for i = 0; i < nSide; i++ { + x := env.GetMinX() + y := env.GetMaxY() - float64(i)*ySegLen + pts[ipt] = gsf.coord(x, y) + ipt++ + } + pts[ipt] = Geom_NewCoordinateFromCoordinate(pts[0]) + ipt++ + + ring := gsf.geomFact.CreateLinearRingFromCoordinates(pts) + poly := gsf.geomFact.CreatePolygonFromLinearRing(ring) + return java.Cast[*Geom_Polygon](gsf.rotate(poly.Geom_Geometry)) +} + +//* @deprecated use createEllipse instead + +func (gsf *Util_GeometricShapeFactory) CreateCircle() *Geom_Polygon { + return gsf.CreateEllipse() +} + +func (gsf *Util_GeometricShapeFactory) CreateEllipse() *Geom_Polygon { + env := gsf.dim.getEnvelope() + xRadius := env.GetWidth() / 2.0 + yRadius := env.GetHeight() / 2.0 + + centreX := env.GetMinX() + xRadius + centreY := env.GetMinY() + yRadius + + pts := make([]*Geom_Coordinate, gsf.nPts+1) + iPt := 0 + for i := 0; i < gsf.nPts; i++ { + ang := float64(i) * (2 * math.Pi / float64(gsf.nPts)) + x := xRadius*Algorithm_Angle_CosSnap(ang) + centreX + y := yRadius*Algorithm_Angle_SinSnap(ang) + centreY + pts[iPt] = gsf.coord(x, y) + iPt++ + } + pts[iPt] = Geom_NewCoordinateFromCoordinate(pts[0]) + + ring := gsf.geomFact.CreateLinearRingFromCoordinates(pts) + poly := gsf.geomFact.CreatePolygonFromLinearRing(ring) + return java.Cast[*Geom_Polygon](gsf.rotate(poly.Geom_Geometry)) +} + +func (gsf *Util_GeometricShapeFactory) CreateSquircle() *Geom_Polygon { + return gsf.CreateSupercircle(4) +} + +func (gsf *Util_GeometricShapeFactory) CreateSupercircle(power float64) *Geom_Polygon { + recipPow := 1.0 / power + + radius := gsf.dim.getMinSize() / 2 + centre := gsf.dim.getCentre() + + r4 := math.Pow(radius, power) + y0 := radius + + xyInt := math.Pow(r4/2, recipPow) + + nSegsInOct := gsf.nPts / 8 + totPts := nSegsInOct*8 + 1 + pts := make([]*Geom_Coordinate, totPts) + xInc := xyInt / float64(nSegsInOct) + + for i := 0; i <= nSegsInOct; i++ { + x := 0.0 + y := y0 + if i != 0 { + x = xInc * float64(i) + x4 := math.Pow(x, power) + y = math.Pow(r4-x4, recipPow) + } + pts[i] = gsf.coordTrans(x, y, centre) + pts[2*nSegsInOct-i] = gsf.coordTrans(y, x, centre) + + pts[2*nSegsInOct+i] = gsf.coordTrans(y, -x, centre) + pts[4*nSegsInOct-i] = gsf.coordTrans(x, -y, centre) + + pts[4*nSegsInOct+i] = gsf.coordTrans(-x, -y, centre) + pts[6*nSegsInOct-i] = gsf.coordTrans(-y, -x, centre) + + pts[6*nSegsInOct+i] = gsf.coordTrans(-y, x, centre) + pts[8*nSegsInOct-i] = gsf.coordTrans(-x, y, centre) + } + pts[len(pts)-1] = Geom_NewCoordinateFromCoordinate(pts[0]) + + ring := gsf.geomFact.CreateLinearRingFromCoordinates(pts) + poly := gsf.geomFact.CreatePolygonFromLinearRing(ring) + return java.Cast[*Geom_Polygon](gsf.rotate(poly.Geom_Geometry)) +} + +func (gsf *Util_GeometricShapeFactory) CreateArc(startAng, angExtent float64) *Geom_LineString { + env := gsf.dim.getEnvelope() + xRadius := env.GetWidth() / 2.0 + yRadius := env.GetHeight() / 2.0 + + centreX := env.GetMinX() + xRadius + centreY := env.GetMinY() + yRadius + + angSize := angExtent + if angSize <= 0.0 || angSize > Algorithm_Angle_PiTimes2 { + angSize = Algorithm_Angle_PiTimes2 + } + angInc := angSize / float64(gsf.nPts-1) + + pts := make([]*Geom_Coordinate, gsf.nPts) + iPt := 0 + for i := 0; i < gsf.nPts; i++ { + ang := startAng + float64(i)*angInc + x := xRadius*Algorithm_Angle_CosSnap(ang) + centreX + y := yRadius*Algorithm_Angle_SinSnap(ang) + centreY + pts[iPt] = gsf.coord(x, y) + iPt++ + } + line := gsf.geomFact.CreateLineStringFromCoordinates(pts) + return java.Cast[*Geom_LineString](gsf.rotate(line.Geom_Geometry)) +} + +func (gsf *Util_GeometricShapeFactory) CreateArcPolygon(startAng, angExtent float64) *Geom_Polygon { + env := gsf.dim.getEnvelope() + xRadius := env.GetWidth() / 2.0 + yRadius := env.GetHeight() / 2.0 + + centreX := env.GetMinX() + xRadius + centreY := env.GetMinY() + yRadius + + angSize := angExtent + if angSize <= 0.0 || angSize > Algorithm_Angle_PiTimes2 { + angSize = Algorithm_Angle_PiTimes2 + } + angInc := angSize / float64(gsf.nPts-1) + // double check = angInc * nPts; + // double checkEndAng = startAng + check; + + pts := make([]*Geom_Coordinate, gsf.nPts+2) + + iPt := 0 + pts[iPt] = gsf.coord(centreX, centreY) + iPt++ + for i := 0; i < gsf.nPts; i++ { + ang := startAng + angInc*float64(i) + + x := xRadius*Algorithm_Angle_CosSnap(ang) + centreX + y := yRadius*Algorithm_Angle_SinSnap(ang) + centreY + pts[iPt] = gsf.coord(x, y) + iPt++ + } + pts[iPt] = gsf.coord(centreX, centreY) + iPt++ + ring := gsf.geomFact.CreateLinearRingFromCoordinates(pts) + poly := gsf.geomFact.CreatePolygonFromLinearRing(ring) + return java.Cast[*Geom_Polygon](gsf.rotate(poly.Geom_Geometry)) +} + +func (gsf *Util_GeometricShapeFactory) coord(x, y float64) *Geom_Coordinate { + pt := Geom_NewCoordinateWithXY(x, y) + gsf.precModel.MakePreciseCoordinate(pt) + return pt +} + +func (gsf *Util_GeometricShapeFactory) coordTrans(x, y float64, trans *Geom_Coordinate) *Geom_Coordinate { + return gsf.coord(x+trans.X, y+trans.Y) +} + +// util_GeometricShapeFactory_Dimensions is the inner Dimensions class. +type util_GeometricShapeFactory_Dimensions struct { + base *Geom_Coordinate + centre *Geom_Coordinate + width float64 + height float64 +} + +func util_newGeometricShapeFactory_Dimensions() *util_GeometricShapeFactory_Dimensions { + return &util_GeometricShapeFactory_Dimensions{} +} + +func (d *util_GeometricShapeFactory_Dimensions) setBase(base *Geom_Coordinate) { d.base = base } +func (d *util_GeometricShapeFactory_Dimensions) getBase() *Geom_Coordinate { return d.base } + +func (d *util_GeometricShapeFactory_Dimensions) setCentre(centre *Geom_Coordinate) { + d.centre = centre +} + +func (d *util_GeometricShapeFactory_Dimensions) getCentre() *Geom_Coordinate { + if d.centre == nil { + d.centre = Geom_NewCoordinateWithXY(d.base.X+d.width/2, d.base.Y+d.height/2) + } + return d.centre +} + +func (d *util_GeometricShapeFactory_Dimensions) setSize(size float64) { + d.height = size + d.width = size +} + +func (d *util_GeometricShapeFactory_Dimensions) getMinSize() float64 { + return math.Min(d.width, d.height) +} + +func (d *util_GeometricShapeFactory_Dimensions) setWidth(width float64) { d.width = width } +func (d *util_GeometricShapeFactory_Dimensions) getWidth() float64 { return d.width } +func (d *util_GeometricShapeFactory_Dimensions) getHeight() float64 { return d.height } +func (d *util_GeometricShapeFactory_Dimensions) setHeight(height float64) { d.height = height } + +func (d *util_GeometricShapeFactory_Dimensions) setEnvelope(env *Geom_Envelope) { + d.width = env.GetWidth() + d.height = env.GetHeight() + d.base = Geom_NewCoordinateWithXY(env.GetMinX(), env.GetMinY()) + d.centre = Geom_NewCoordinateFromCoordinate(env.Centre()) +} + +func (d *util_GeometricShapeFactory_Dimensions) getEnvelope() *Geom_Envelope { + if d.base != nil { + return Geom_NewEnvelopeFromXY(d.base.X, d.base.X+d.width, d.base.Y, d.base.Y+d.height) + } + if d.centre != nil { + return Geom_NewEnvelopeFromXY(d.centre.X-d.width/2, d.centre.X+d.width/2, + d.centre.Y-d.height/2, d.centre.Y+d.height/2) + } + return Geom_NewEnvelopeFromXY(0, d.width, 0, d.height) +}