Skip to content

Commit b0b407e

Browse files
authored
Merge pull request #2 from stolstov/geometry-collections
Various fixes and TODOs
2 parents 104c2ef + 7b2bc6a commit b0b407e

7 files changed

Lines changed: 398 additions & 113 deletions

File tree

src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java

Lines changed: 77 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package com.esri.core.geometry.ogc;
2626

2727
import com.esri.core.geometry.Envelope;
28+
import com.esri.core.geometry.GeoJsonExportFlags;
2829
import com.esri.core.geometry.Geometry;
2930
import com.esri.core.geometry.GeometryCursor;
3031
import com.esri.core.geometry.GeometryException;
@@ -35,15 +36,15 @@
3536
import com.esri.core.geometry.OGCStructureInternal;
3637
import com.esri.core.geometry.OperatorConvexHull;
3738
import com.esri.core.geometry.OperatorDifference;
39+
import com.esri.core.geometry.OperatorExportToGeoJson;
40+
import com.esri.core.geometry.OperatorIntersection;
41+
import com.esri.core.geometry.OperatorUnion;
42+
import com.esri.core.geometry.Point;
3843
import com.esri.core.geometry.Polygon;
3944
import com.esri.core.geometry.Polyline;
4045
import com.esri.core.geometry.SimpleGeometryCursor;
4146
import com.esri.core.geometry.SpatialReference;
4247
import com.esri.core.geometry.VertexDescription;
43-
import com.esri.core.geometry.GeoJsonExportFlags;
44-
import com.esri.core.geometry.OperatorExportToGeoJson;
45-
import com.esri.core.geometry.OperatorUnion;
46-
import com.esri.core.geometry.Point;
4748

4849
import java.nio.ByteBuffer;
4950
import java.nio.ByteOrder;
@@ -546,9 +547,11 @@ public double distance(OGCGeometry another) {
546547

547548
double minD = Double.NaN;
548549
for (int i = 0, n = numGeometries(); i < n; ++i) {
550+
// TODO Skip expensive distance computation if bounding boxes are further away than minD
549551
double d = geometryN(i).distance(another);
550552
if (d < minD || Double.isNaN(minD)) {
551553
minD = d;
554+
// TODO Replace zero with tolerance defined by the spatial reference
552555
if (minD == 0) {
553556
break;
554557
}
@@ -646,93 +649,102 @@ public boolean Equals(OGCGeometry another) {
646649

647650
OGCConcreteGeometryCollection gc1 = (OGCConcreteGeometryCollection)g1;
648651
OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection)g2;
652+
// TODO Assuming input geometries are simple and valid, remove-overlaps would be a no-op.
653+
// Hence, calling flatten() should be sufficient.
649654
gc1 = gc1.flattenAndRemoveOverlaps();
650655
gc2 = gc2.flattenAndRemoveOverlaps();
651656
int n = gc1.numGeometries();
652657
if (n != gc2.numGeometries()) {
653658
return false;
654659
}
655660

656-
boolean res = false;
657661
for (int i = 0; i < n; ++i) {
658662
if (!gc1.geometryN(i).Equals(gc2.geometryN(i))) {
659663
return false;
660664
}
661-
662-
res = true;
663665
}
664666

665-
return res;
667+
return n > 0;
666668
}
667-
669+
670+
private static OGCConcreteGeometryCollection toGeometryCollection(OGCGeometry geometry)
671+
{
672+
if (geometry.geometryType() != OGCConcreteGeometryCollection.TYPE) {
673+
return new OGCConcreteGeometryCollection(geometry, geometry.getEsriSpatialReference());
674+
}
675+
676+
return (OGCConcreteGeometryCollection) geometry;
677+
}
678+
679+
private static List<Geometry> toList(GeometryCursor cursor)
680+
{
681+
List<Geometry> geometries = new ArrayList<Geometry>();
682+
for (Geometry geometry = cursor.next(); geometry != null; geometry = cursor.next()) {
683+
geometries.add(geometry);
684+
}
685+
return geometries;
686+
}
687+
668688
//Topological
669689
@Override
670690
public OGCGeometry difference(OGCGeometry another) {
671-
List<OGCConcreteGeometryCollection> list = wrapGeomsIntoList_(this, another);
672-
list = prepare_for_ops_(list);
673-
if (list.size() != 2) // this should not happen
674-
throw new GeometryException("internal error");
675-
691+
if (isEmpty() || another.isEmpty()) {
692+
return this;
693+
}
694+
695+
List<Geometry> geometries = toList(prepare_for_ops_(toGeometryCollection(this)));
696+
List<Geometry> otherGeometries = toList(prepare_for_ops_(toGeometryCollection(another)));
697+
676698
List<OGCGeometry> result = new ArrayList<OGCGeometry>();
677-
OGCConcreteGeometryCollection coll1 = list.get(0);
678-
OGCConcreteGeometryCollection coll2 = list.get(1);
679-
for (int i = 0, n = coll1.numGeometries(); i < n; ++i) {
680-
OGCGeometry cur = coll1.geometryN(i);
681-
for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) {
682-
OGCGeometry geom2 = coll2.geometryN(j);
683-
if (cur.dimension() > geom2.dimension())
699+
for (Geometry geometry : geometries) {
700+
for (Geometry otherGeometry : otherGeometries) {
701+
if (geometry.getDimension() > otherGeometry.getDimension()) {
684702
continue; //subtracting lower dimension has no effect.
685-
686-
cur = cur.difference(geom2);
687-
if (cur.isEmpty())
703+
}
704+
705+
geometry = OperatorDifference.local().execute(geometry, otherGeometry, esriSR, null);
706+
if (geometry.isEmpty()) {
688707
break;
708+
}
709+
}
710+
711+
if (!geometry.isEmpty()) {
712+
result.add(OGCGeometry.createFromEsriGeometry(geometry, esriSR));
689713
}
690-
691-
if (cur.isEmpty())
692-
continue;
693-
694-
result.add(cur);
695714
}
696-
715+
697716
if (result.size() == 1) {
698717
return result.get(0).reduceFromMulti();
699718
}
700719

701-
return new OGCConcreteGeometryCollection(result, esriSR);
720+
return new OGCConcreteGeometryCollection(result, esriSR).flattenAndRemoveOverlaps();
702721
}
703722

704723
@Override
705724
public OGCGeometry intersection(OGCGeometry another) {
706725
if (isEmpty() || another.isEmpty()) {
707726
return new OGCConcreteGeometryCollection(esriSR);
708727
}
709-
710-
List<OGCConcreteGeometryCollection> list = wrapGeomsIntoList_(this, another);
711-
list = prepare_for_ops_(list);
712-
if (list.size() != 2) // this should not happen
713-
throw new GeometryException("internal error");
714-
728+
729+
List<Geometry> geometries = toList(prepare_for_ops_(toGeometryCollection(this)));
730+
List<Geometry> otherGeometries = toList(prepare_for_ops_(toGeometryCollection(another)));
731+
715732
List<OGCGeometry> result = new ArrayList<OGCGeometry>();
716-
OGCConcreteGeometryCollection coll1 = list.get(0);
717-
OGCConcreteGeometryCollection coll2 = list.get(1);
718-
for (int i = 0, n = coll1.numGeometries(); i < n; ++i) {
719-
OGCGeometry cur = coll1.geometryN(i);
720-
for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) {
721-
OGCGeometry geom2 = coll2.geometryN(j);
722-
723-
OGCGeometry partialIntersection = cur.intersection(geom2);
724-
if (partialIntersection.isEmpty())
725-
continue;
726-
727-
result.add(partialIntersection);
733+
for (Geometry geometry : geometries) {
734+
for (Geometry otherGeometry : otherGeometries) {
735+
GeometryCursor intersectionCursor = OperatorIntersection.local().execute(new SimpleGeometryCursor(geometry), new SimpleGeometryCursor(otherGeometry), esriSR, null, 7);
736+
OGCGeometry intersection = OGCGeometry.createFromEsriCursor(intersectionCursor, esriSR, true);
737+
if (!intersection.isEmpty()) {
738+
result.add(intersection);
739+
}
728740
}
729741
}
730742

731743
if (result.size() == 1) {
732744
return result.get(0).reduceFromMulti();
733745
}
734746

735-
return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps();
747+
return new OGCConcreteGeometryCollection(result, esriSR).flattenAndRemoveOverlaps();
736748
}
737749

738750
@Override
@@ -741,21 +753,6 @@ public OGCGeometry symDifference(OGCGeometry another) {
741753
throw new UnsupportedOperationException();
742754
}
743755

744-
//make a list of collections out of two geometries
745-
private static List<OGCConcreteGeometryCollection> wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) {
746-
List<OGCConcreteGeometryCollection> list = new ArrayList<OGCConcreteGeometryCollection>();
747-
if (g1.geometryType() != OGCConcreteGeometryCollection.TYPE) {
748-
g1 = new OGCConcreteGeometryCollection(g1, g1.getEsriSpatialReference());
749-
}
750-
if (g2.geometryType() != OGCConcreteGeometryCollection.TYPE) {
751-
g2 = new OGCConcreteGeometryCollection(g2, g2.getEsriSpatialReference());
752-
}
753-
754-
list.add((OGCConcreteGeometryCollection)g1);
755-
list.add((OGCConcreteGeometryCollection)g2);
756-
757-
return list;
758-
}
759756
/**
760757
* Checks if collection is flattened.
761758
* @return True for the flattened collection. A flattened collection contains up to three non-empty geometries:
@@ -867,24 +864,23 @@ public OGCConcreteGeometryCollection flatten() {
867864
/**
868865
* Fixes topological overlaps in the GeometryCollecion.
869866
* This is equivalent to union of the geometry collection elements.
870-
*
867+
*
868+
* TODO "flattened" collection is supposed to contain only mutli-geometries, but this method may return single geometries
869+
* e.g. for GEOMETRYCOLLECTION (LINESTRING (...)) it returns GEOMETRYCOLLECTION (LINESTRING (...))
870+
* and not GEOMETRYCOLLECTION (MULTILINESTRING (...))
871871
* @return A geometry collection that is flattened and has no overlapping elements.
872872
*/
873873
public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() {
874-
ArrayList<Geometry> geoms = new ArrayList<Geometry>();
875-
874+
876875
//flatten and crack/cluster
877876
GeometryCursor cursor = OGCStructureInternal.prepare_for_ops_(flatten().getEsriGeometryCursor(), esriSR);
878-
for (Geometry g = cursor.next(); g != null; g = cursor.next()) {
879-
geoms.add(g);
880-
}
881-
877+
882878
//make sure geometries don't overlap
883-
return removeOverlapsHelper_(geoms);
879+
return new OGCConcreteGeometryCollection(removeOverlapsHelper_(toList(cursor)), esriSR);
884880
}
885-
886-
private OGCConcreteGeometryCollection removeOverlapsHelper_(List<Geometry> geoms) {
887-
ArrayList<Geometry> result = new ArrayList<Geometry>();
881+
882+
private GeometryCursor removeOverlapsHelper_(List<Geometry> geoms) {
883+
List<Geometry> result = new ArrayList<Geometry>();
888884
for (int i = 0; i < geoms.size(); ++i) {
889885
Geometry current = geoms.get(i);
890886
if (current.isEmpty())
@@ -903,7 +899,7 @@ private OGCConcreteGeometryCollection removeOverlapsHelper_(List<Geometry> geoms
903899
result.add(current);
904900
}
905901

906-
return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(result), esriSR);
902+
return new SimpleGeometryCursor(result);
907903
}
908904

909905
private static class FlatteningCollectionCursor extends GeometryCursor {
@@ -954,36 +950,9 @@ public int getGeometryID() {
954950
//Collectively processes group of geometry collections (intersects all segments and clusters points).
955951
//Flattens collections, removes overlaps.
956952
//Once done, the result collections would work well for topological and relational operations.
957-
private List<OGCConcreteGeometryCollection> prepare_for_ops_(List<OGCConcreteGeometryCollection> geoms) {
958-
assert(geoms != null && !geoms.isEmpty());
959-
GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(new FlatteningCollectionCursor(geoms), esriSR);
960-
961-
List<OGCConcreteGeometryCollection> result = new ArrayList<OGCConcreteGeometryCollection>();
962-
int prevCollectionIndex = -1;
963-
List<Geometry> list = null;
964-
for (Geometry g = prepared.next(); g != null; g = prepared.next()) {
965-
int c = prepared.getGeometryID();
966-
if (c != prevCollectionIndex) {
967-
//add empty collections for all skipped indices
968-
for (int i = prevCollectionIndex; i < c - 1; i++) {
969-
result.add(new OGCConcreteGeometryCollection(esriSR));
970-
}
971-
972-
if (list != null) {
973-
result.add(removeOverlapsHelper_(list));
974-
}
975-
976-
list = new ArrayList<Geometry>();
977-
prevCollectionIndex = c;
978-
}
979-
980-
list.add(g);
981-
}
982-
983-
if (list != null) {
984-
result.add(removeOverlapsHelper_(list));
985-
}
986-
987-
return result;
953+
private GeometryCursor prepare_for_ops_(OGCConcreteGeometryCollection collection) {
954+
assert(collection != null && !collection.isEmpty());
955+
GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(collection.flatten().getEsriGeometryCursor(), esriSR);
956+
return removeOverlapsHelper_(toList(prepared));
988957
}
989958
}

src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ public boolean contains(OGCGeometry another) {
329329

330330
public boolean overlaps(OGCGeometry another) {
331331
if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) {
332-
return another.overlaps(this); //overlaps should be symmetric
332+
// TODO
333+
throw new UnsupportedOperationException();
333334
}
334335

335336
com.esri.core.geometry.Geometry geom1 = getEsriGeometry();
@@ -530,9 +531,11 @@ public OGCGeometry difference(OGCGeometry another) {
530531
}
531532

532533
public OGCGeometry symDifference(OGCGeometry another) {
533-
if (another.geometryType() == OGCConcreteGeometryCollection.TYPE)
534-
return another.symDifference(this);
535-
534+
if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) {
535+
// TODO
536+
throw new UnsupportedOperationException();
537+
}
538+
536539
com.esri.core.geometry.Geometry geom1 = getEsriGeometry();
537540
com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry();
538541
return createFromEsriGeometry(

src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public OGCGeometry convertToMulti()
139139
public OGCGeometry reduceFromMulti() {
140140
int n = numGeometries();
141141
if (n == 0) {
142-
return new OGCLineString(new Polygon(polygon.getDescription()), 0, esriSR);
142+
return new OGCPolygon(new Polygon(polygon.getDescription()), 0, esriSR);
143143
}
144144

145145
if (n == 1) {

0 commit comments

Comments
 (0)