From ad5fb8451e49ce334e11f2f5789dba0d7963bb2f Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 11 Dec 2014 15:36:35 -0800 Subject: [PATCH 001/116] fixes for release 1.2.1 --- .../java/com/esri/core/geometry/Bufferer.java | 4 +- .../esri/core/geometry/CrackAndCluster.java | 18 +- .../java/com/esri/core/geometry/Cracker.java | 111 +- .../java/com/esri/core/geometry/Cutter.java | 2 +- .../com/esri/core/geometry/EditShape.java | 83 +- .../java/com/esri/core/geometry/Envelope.java | 30 + .../geometry/Envelope2DIntersectorImpl.java | 1 + .../java/com/esri/core/geometry/Geometry.java | 43 +- .../core/geometry/GeometryAccelerators.java | 25 +- .../esri/core/geometry/GeometryException.java | 12 +- .../com/esri/core/geometry/InternalUtils.java | 202 +- .../esri/core/geometry/JsonStringWriter.java | 740 +++---- .../com/esri/core/geometry/JsonWriter.java | 76 +- .../java/com/esri/core/geometry/Line.java | 66 +- .../com/esri/core/geometry/MapGeometry.java | 2 +- .../com/esri/core/geometry/MathUtils.java | 69 +- .../com/esri/core/geometry/MultiPath.java | 5 + .../com/esri/core/geometry/MultiPathImpl.java | 72 +- .../com/esri/core/geometry/MultiPoint.java | 5 + .../geometry/MultiVertexGeometryImpl.java | 39 +- .../esri/core/geometry/NonSimpleResult.java | 87 +- .../geometry/OperatorSimplifyLocalHelper.java | 27 +- .../geometry/PairwiseIntersectorImpl.java | 249 +++ .../geometry/PlaneSweepCrackerHelper.java | 88 +- .../java/com/esri/core/geometry/Point.java | 14 + .../core/geometry/PointInPolygonHelper.java | 111 +- .../java/com/esri/core/geometry/Polygon.java | 35 +- .../com/esri/core/geometry/PolygonUtils.java | 34 +- .../java/com/esri/core/geometry/Polyline.java | 9 + .../com/esri/core/geometry/QuadTreeImpl.java | 15 +- .../core/geometry/RelationalOperations.java | 975 ++++----- .../geometry/RelationalOperationsMatrix.java | 1798 +++++++++++------ .../core/geometry/SegmentIntersector.java | 20 +- .../com/esri/core/geometry/Simplificator.java | 13 +- .../com/esri/core/geometry/TopoGraph.java | 174 +- .../core/geometry/TopologicalOperations.java | 89 +- .../com/esri/core/geometry/TestEditShape.java | 8 +- .../com/esri/core/geometry/TestGeodetic.java | 9 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 22 +- .../com/esri/core/geometry/TestPoint.java | 18 + .../com/esri/core/geometry/TestPolygon.java | 44 +- .../com/esri/core/geometry/TestQuadTree.java | 4 + .../com/esri/core/geometry/TestRelation.java | 1082 +++++++--- .../com/esri/core/geometry/TestSimplify.java | 17 + 44 files changed, 4381 insertions(+), 2166 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index a8517844..587287fd 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -996,7 +996,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, EditShape edit_shape = new EditShape(); int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, ipath, true); - edit_shape.filterClosePoints(m_filter_tolerance, false); + edit_shape.filterClosePoints(m_filter_tolerance, false, false); if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. // Wither bail out or // produce a circle. @@ -1429,7 +1429,7 @@ && isGap_(pt_before_before, pt_current, edit_shape.setXY(iprev, pt_middle); } - edit_shape.filterClosePoints(m_filter_tolerance, false); + edit_shape.filterClosePoints(m_filter_tolerance, false, false); if (!b_filtered) break; diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index 2a5c6286..6f6d8247 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -30,6 +30,7 @@ final class CrackAndCluster { private EditShape m_shape = null; private ProgressTracker m_progressTracker = null; private double m_tolerance; + private boolean m_filter_degenerate_segments = true; private CrackAndCluster(ProgressTracker progressTracker) { m_progressTracker = progressTracker; @@ -60,10 +61,11 @@ else if (rank2 < rank1) } public static boolean execute(EditShape shape, double tolerance, - ProgressTracker progressTracker) { + ProgressTracker progressTracker, boolean filter_degenerate_segments) { CrackAndCluster cracker = new CrackAndCluster(progressTracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; + cracker.m_filter_degenerate_segments = filter_degenerate_segments; return cracker._do(); } @@ -121,12 +123,14 @@ private boolean _do() { // clamp them // together. bChanged |= bClustered; - - boolean bFiltered = (m_shape.filterClosePoints( - tolerance_for_clustering, true) != 0); // remove all - // degenerate - // segments. - bChanged |= bFiltered; + + if (m_filter_degenerate_segments) { + boolean bFiltered = (m_shape.filterClosePoints( + tolerance_for_clustering, true, false) != 0); // remove all + // degenerate + // segments. + bChanged |= bFiltered; + } boolean b_cracked = false; if (iter == 0 diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index 98b4a909..cd8b9a72 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -30,7 +30,7 @@ * Finds and splits all intersecting segments. Used by the TopoGraph and * Simplify. */ -class Cracker { +final class Cracker { private EditShape m_shape; private ProgressTracker m_progress_tracker; private NonSimpleResult m_non_simple_result; @@ -39,7 +39,19 @@ class Cracker { private SweepComparator m_sweep_comparator; private boolean m_bAllowCoincident; - boolean crackBruteForce_() { + private Segment getSegment_(int vertex, Line lineHelper) { + Segment seg = m_shape.getSegment(vertex); + if (seg == null) { + if (!m_shape.queryLineConnector(vertex, lineHelper)) + return null; + + seg = (Segment)lineHelper; + } + + return seg; + } + + private boolean crackBruteForce_() { EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); boolean b_cracked = false; Line line_1 = new Line(); @@ -59,24 +71,25 @@ boolean crackBruteForce_() { int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); Segment seg_1 = null; + boolean seg_1_zero = false; if (!Geometry.isPoint(GT_1)) { - seg_1 = m_shape.getSegment(vertex_1); - - if (seg_1 == null) { - if (!m_shape.queryLineConnector(vertex_1, line_1)) - continue; - seg_1 = line_1; - line_1.queryEnvelope2D(seg_1_env); - } else { - seg_1.queryEnvelope2D(seg_1_env); - } - + seg_1 = getSegment_(vertex_1, line_1); + if (seg_1 == null) + continue; + + seg_1.queryEnvelope2D(seg_1_env); seg_1_env.inflate(m_tolerance, m_tolerance); if (seg_1.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - continue; + if (seg_1.isDegenerate(0)) { + seg_1_zero = true; + seg_1 = null; + } + else { + continue; + } } } @@ -90,20 +103,24 @@ boolean crackBruteForce_() { int GT_2 = m_shape.getGeometryType(iter_2.currentGeometry()); Segment seg_2 = null; + boolean seg_2_zero = false; if (!Geometry.isPoint(GT_2)) { - seg_2 = m_shape.getSegment(vertex_2); + seg_2 = getSegment_(vertex_2, line_2); if (seg_2 == null) { - if (!m_shape.queryLineConnector(vertex_2, line_2)) - continue; - seg_2 = line_2; - line_2.queryEnvelope2D(seg_2_env); - } else - seg_2.queryEnvelope2D(seg_2_env); - + continue; + } + + seg_2.queryEnvelope2D(seg_2_env); if (seg_2.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - continue; + if (seg_2.isDegenerate(0)) { + seg_2_zero = true; + seg_2 = null; + } + else { + continue; + } } } @@ -141,8 +158,29 @@ boolean crackBruteForce_() { if (split_count_1 > 0) { m_shape.splitSegment_(vertex_1, segment_intersector, 0, true); - m_shape.setPoint(vertex_2, - segment_intersector.getResultPoint()); + if (seg_2_zero) { + //seg_2 was zero length. Need to change all coincident points + //segment at vertex_2 is dzero length, change all attached zero length segments + int v_to = -1; + for (int v = m_shape.getNextVertex(vertex_2); v != -1 && v != vertex_2; v = m_shape.getNextVertex(v)) + { + seg_2 = getSegment_(v, line_2); + v_to = v; + if (seg_2 == null || !seg_2.isDegenerate(0)) + break; + } + //change from vertex_2 to v_to (inclusive). + for (int v = vertex_2; v != -1; v = m_shape.getNextVertex(v)) + { + m_shape.setPoint(v, segment_intersector.getResultPoint()); + if (v == v_to) + break; + } + } + else { + m_shape.setPoint(vertex_2, + segment_intersector.getResultPoint()); + } } segment_intersector.clear(); } @@ -160,8 +198,27 @@ boolean crackBruteForce_() { if (split_count_2 > 0) { m_shape.splitSegment_(vertex_2, segment_intersector, 0, true); - m_shape.setPoint(vertex_1, - segment_intersector.getResultPoint()); + if (seg_1_zero) { + //seg_1 was zero length. Need to change all coincident points + //segment at vertex_2 is dzero length, change all attached zero length segments + int v_to = -1; + for (int v = m_shape.getNextVertex(vertex_1); v != -1 && v != vertex_1; v = m_shape.getNextVertex(v)) { + seg_2 = getSegment_(v, line_2);//using here seg_2 for seg_1 + v_to = v; + if (seg_2 == null || !seg_2.isDegenerate(0)) + break; + } + //change from vertex_2 to v_to (inclusive). + for (int v = vertex_1; v != -1; v = m_shape.getNextVertex(v)) { + m_shape.setPoint(v, segment_intersector.getResultPoint()); + if (v == v_to) + break; + } + } + else { + m_shape.setPoint(vertex_1, + segment_intersector.getResultPoint()); + } } segment_intersector.clear(); } diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index f3f17efe..d575e1de 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -116,7 +116,7 @@ static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, EditShape editShape = new EditShape(); int cutteeHandle = editShape.addGeometry(cuttee); int cutterHandle = editShape.addGeometry(cutter); - CrackAndCluster.execute(editShape, tolerance, progressTracker); + CrackAndCluster.execute(editShape, tolerance, progressTracker, true); int order = 0; int orderIndex = editShape.createUserIndex(); diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 84557893..a85ca5a2 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -25,6 +25,8 @@ import java.util.ArrayList; +import com.esri.core.geometry.Geometry.GeometryType; + /** * A helper geometry structure that can store MultiPoint, Polyline, Polygon * geometries in linked lists. It allows constant time manipulation of geometry @@ -204,7 +206,7 @@ int newPath_(int geom) { // m_path_index_list.set(index + 4, -1);//first vertex handle // m_path_index_list.set(index + 5, -1);//last vertex handle m_path_index_list.setField(index, 6, 0);// path flags - m_path_index_list.setField(index, 7, geom);// geometry handle + setPathGeometry_(index, geom); if (pindex >= m_path_areas.size()) { int sz = pindex < 16 ? 16 : (pindex * 3) / 2; m_path_areas.resize(sz); @@ -331,10 +333,25 @@ Point getHelperPoint_() { m_helper_point = new Point(m_vertices.getDescription()); return m_helper_point; } + + void setFillRule(int geom, int rule) { + int t = m_geometry_index_list.getField(geom, 2); + t &= ~(0x8000000); + t |= rule == Polygon.FillRule.enumFillRuleWinding ? 0x8000000 : 0; + m_geometry_index_list.setField(geom, 2, t);//fill rule combined with geometry type + } + int getFillRule(int geom) { + int t = m_geometry_index_list.getField(geom, 2); + return (t & 0x8000000) != 0 ? Polygon.FillRule.enumFillRuleWinding : Polygon.FillRule.enumFillRuleOddEven; + } + int addMultiPath_(MultiPath multi_path) { int newgeom = createGeometry(multi_path.getType(), multi_path.getDescription()); + if (multi_path.getType() == Geometry.Type.Polygon) + setFillRule(newgeom, ((Polygon)multi_path).getFillRule()); + appendMultiPath_(newgeom, multi_path); return newgeom; } @@ -585,6 +602,8 @@ int addPathFromMultiPath(MultiPath multi_path, int ipath, boolean as_polygon) { : Geometry.Type.Polyline, multi_path.getDescription()); MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); + if (multi_path.getPathSize(ipath) < 2) + return newgeom; //return empty geometry // m_vertices->reserve_rounded(m_vertices->get_point_count() + // multi_path.get_path_size(ipath));//ensure reallocation happens by @@ -821,7 +840,7 @@ int getPrevGeometry(int geom) { // Returns the type of the Geometry. int getGeometryType(int geom) { - return m_geometry_index_list.getField(geom, 2); + return m_geometry_index_list.getField(geom, 2) & 0x7FFFFFFF; } // Sets value to the given user index on a geometry. @@ -898,10 +917,13 @@ int getPathCount(int geom) { // 0 if no segments have been removed. // When b_remove_last_vertices and the result path is < 3 for polygon or < 2 // for polyline, it'll be removed. - int filterClosePoints(double tolerance, boolean b_remove_last_vertices) { + int filterClosePoints(double tolerance, boolean b_remove_last_vertices, boolean only_polygons) { int res = 0; for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { - if (!Geometry.isMultiPath(getGeometryType(geometry))) + int gt = getGeometryType(geometry); + if (!Geometry.isMultiPath(gt)) + continue; + if (only_polygons && gt != GeometryType.Polygon) continue; boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; @@ -1339,14 +1361,18 @@ void setWeight(int vertex, double weight) { } int vindex = getVertexIndex(vertex); + if (vindex >= m_weights.size()) { + m_weights.resize(vindex + 1, 1.0); + } + m_weights.write(vindex, weight); } double getWeight(int vertex) { - if (m_weights == null) - return 1.0; - int vindex = getVertexIndex(vertex); + if (m_weights == null || vindex >= m_weights.size()) + return 1.0; + return m_weights.read(vindex); } @@ -2040,8 +2066,7 @@ void interpolateAttributesForClosedPath_(int semantics, int path, } cumulative_length += segment_length; double t = cumulative_length / sub_length; - prev_interpolated_attribute = (1.0 - t) * from_attribute + t - * to_attribute; + prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); } return; @@ -2280,5 +2305,43 @@ boolean hasPointFeatures() } return false; } - + + void swapGeometry(int geom1, int geom2) + { + int first_path1 = getFirstPath(geom1); + int first_path2 = getFirstPath(geom2); + int last_path1 = getLastPath(geom1); + int last_path2 = getLastPath(geom2); + + for (int path = getFirstPath(geom1); path != -1; path = getNextPath(path)) + { + setPathGeometry_(path, geom2); + } + + for (int path = getFirstPath(geom2); path != -1; path = getNextPath(path)) + { + setPathGeometry_(path, geom1); + } + + setFirstPath_(geom1, first_path2); + setFirstPath_(geom2, first_path1); + setLastPath_(geom1, last_path2); + setLastPath_(geom2, last_path1); + + int vc1 = getPointCount(geom1); + int pc1 = getPathCount(geom1); + int vc2 = getPointCount(geom2); + int pc2 = getPathCount(geom2); + + setGeometryVertexCount_(geom1, vc2); + setGeometryVertexCount_(geom2, vc1); + setGeometryPathCount_(geom1, pc2); + setGeometryPathCount_(geom2, pc1); + + int gt1 = m_geometry_index_list.getField(geom1, 2); + int gt2 = m_geometry_index_list.getField(geom2, 2); + m_geometry_index_list.setField(geom1, 2, gt2); + m_geometry_index_list.setField(geom2, 2, gt1); + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 0675a1e8..9d74440b 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1113,4 +1113,34 @@ public void setYMax(double y) { public Geometry getBoundary() { return Boundary.calculate(this, null); } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + Envelope1D interval = queryInterval(semantics, i); + if (interval.isEmpty()) { + interval.vmin = value; + interval.vmax = value; + setInterval(semantics, i, interval); + } + } + } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + if (isEmpty()) + return "Envelope: []"; + + String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmin + ", " + m_envelope.ymin +"]"; + return s; + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 5fdb9fc3..d9430f96 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -43,6 +43,7 @@ void startConstruction() { m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); } else { + m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index d4cd9952..6e16c0e6 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -50,8 +50,7 @@ public abstract class Geometry implements Serializable { /** * Geometry types */ - static interface GeometryType { - + static public interface GeometryType { public final static int Unknown = 0; public final static int Point = 1 + 0x20; // points public final static int Line = 2 + 0x40 + 0x100; // lines, segment @@ -59,10 +58,10 @@ static interface GeometryType { final static int EllipticArc = 4 + 0x40 + 0x100; // lines, segment public final static int Envelope = 5 + 0x40 + 0x80; // lines, areas public final static int MultiPoint = 6 + 0x20 + 0x200; // points, - // multivertex + // multivertex public final static int Polyline = 7 + 0x40 + 0x200 + 0x400; // lines, // multivertex, - // multipath + // multipath public final static int Polygon = 8 + 0x40 + 0x80 + 0x200 + 0x400; } @@ -509,7 +508,15 @@ public Geometry copy() { * boundary is a Multi_point consisting of path endpoints. For Multi_point * and Point NULL is returned. */ - public abstract Geometry getBoundary(); + public abstract Geometry getBoundary(); + + /** + * Replaces NaNs in the attribute with the given value. + * If the geometry is not empty, it adds the attribute if geometry does not have it yet, and replaces the values. + * If the geometry is empty, it adds the attribute and does not set any values. + * + */ + public abstract void replaceNaNs(int semantics, double value); static Geometry _clone(Geometry src) { Geometry geom = src.createInstance(); @@ -578,4 +585,30 @@ public String toString() { } } + /** + *Returns count of geometry vertices: + *1 for Point, 4 for Envelope, get_point_count for MultiVertexGeometry types, + *2 for segment types + *Returns 0 if geometry is empty. + */ + public static int vertex_count(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (Geometry.isMultiVertex(gt.value())) + return ((MultiVertexGeometry)geom).getPointCount(); + + if (geom.isEmpty()) + return 0; + + if (gt == Geometry.Type.Envelope) + return 4; + + if (gt == Geometry.Type.Point) + return 1; + + if (Geometry.isSegment(gt.value())) + return 2; + + throw new GeometryException("missing type"); + } + } diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 18140ee3..138b134e 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -29,7 +29,7 @@ class GeometryAccelerators { private RasterizedGeometry2D m_rasterizedGeometry; private QuadTreeImpl m_quad_tree; - private ArrayList m_path_envelopes; + private QuadTreeImpl m_quad_tree_for_paths; public RasterizedGeometry2D getRasterizedGeometry() { return m_rasterizedGeometry; @@ -39,8 +39,8 @@ public QuadTreeImpl getQuadTree() { return m_quad_tree; } - public ArrayList getPathEnvelopes() { - return m_path_envelopes; + public QuadTreeImpl getQuadTreeForPaths() { + return m_quad_tree_for_paths; } void _setRasterizedGeometry(RasterizedGeometry2D rg) { @@ -51,9 +51,7 @@ void _setQuadTree(QuadTreeImpl quad_tree) { m_quad_tree = quad_tree; } - void _setPathEnvelopes(ArrayList pe) { - m_path_envelopes = pe; - } + void _setQuadTreeForPaths(QuadTreeImpl quad_tree) { m_quad_tree_for_paths = quad_tree; } static boolean canUseRasterizedGeometry(Geometry geom) { if (geom.isEmpty() @@ -77,12 +75,13 @@ static boolean canUseQuadTree(Geometry geom) { return true; } - static boolean canUsePathEnvelopes(Geometry geom) { - if (geom.isEmpty() - || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { - return false; - } + static boolean canUseQuadTreeForPaths(Geometry geom) { + if (geom.isEmpty() || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) + return false; - return true; - } + if (((MultiVertexGeometry) geom).getPointCount() < 20) + return false; + + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 07b11ffd..41ea2984 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -31,10 +31,6 @@ public class GeometryException extends RuntimeException { private static final long serialVersionUID = 1L; - /** - * The internal code for geometry exception. - */ - public int internalCode; /** * Constructs a Geometry Exception with the given error string/message. @@ -42,14 +38,8 @@ public class GeometryException extends RuntimeException { * @param str * - The error string. */ - GeometryException(String str) { - super(str); - internalCode = 0; - } - - GeometryException(String str, int sgCode) { + public GeometryException(String str) { super(str); - internalCode = sgCode; } static GeometryException GeometryInternalError() { diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index 90c84f48..42bd8362 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -26,7 +26,7 @@ import java.util.ArrayList; -class InternalUtils { +final class InternalUtils { // p0 and p1 have to be on left/right boundary of fullRange2D (since this // fuction can be called recursively, p0 or p1 can also be fullRange2D @@ -243,6 +243,7 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); Envelope2D boundingbox = new Envelope2D(); boolean resized_extent = false; + while (seg_iter.nextPath()) { while (seg_iter.hasNextSegment()) { Segment segment = seg_iter.nextSegment(); @@ -306,6 +307,48 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, return quad_tree_impl; } + static QuadTreeImpl buildQuadTreeForPaths(MultiPathImpl multipathImpl) + { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + if (extent.isEmpty()) + return null; + + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + + boolean resized_extent = false; + do + { + for (int ipath = 0, npaths = multipathImpl.getPathCount(); ipath < npaths; ipath++) + { + multipathImpl.queryPathEnvelope2D(ipath, boundingbox); + hint_index = quad_tree_impl.insert(ipath, boundingbox, hint_index); + + if (hint_index == -1) + { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + //This is usually happens because esri shape buffer contains geometry extent which is slightly different from the true extent. + //Recalculate extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + break; //break the for loop + } + else + { + resized_extent = false; + } + } + + } while(resized_extent); + + return quad_tree_impl; + } + static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { Envelope2D extent = new Envelope2D(); multipointImpl.queryLooseEnvelope2D(extent); @@ -431,115 +474,94 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( return intersector; } - static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( - MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { - int type_a = multipathImplA.getType().value(); - int type_b = multipathImplB.getType().value(); + static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( + MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, + double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { + int type_a = multipathImplA.getType().value(); + int type_b = multipathImplB.getType().value(); - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipathImplA.queryLooseEnvelope2D(env_a); - multipathImplB.queryLooseEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathImplA.queryLooseEnvelope2D(env_a); + multipathImplB.queryLooseEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); - Envelope2D envInter = new Envelope2D(); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - GeometryAccelerators accel_a = multipathImplA._getAccelerators(); - ArrayList path_envelopes_a = null; - - if (accel_a != null) { - path_envelopes_a = accel_a.getPathEnvelopes(); - } + Envelope2D envInter = new Envelope2D(); + envInter.setCoords(env_a); + envInter.intersect(env_b); - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); - intersector.setTolerance(tolerance); + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(tolerance); - boolean b_found_red = false; - intersector.startRedConstruction(); - for (int ipath_a = 0; ipath_a < multipathImplA.getPathCount(); ipath_a++) { - if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon - && !multipathImplA.isExteriorRing(ipath_a)) { - continue; - } + boolean b_found_red = false; + intersector.startRedConstruction(); + for (int ipath_a = 0, npaths = multipathImplA.getPathCount(); ipath_a < npaths; ipath_a++) { + if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon && !multipathImplA.isExteriorRing(ipath_a)) + continue; - if (path_envelopes_a != null) { - Envelope2D env = path_envelopes_a.get(ipath_a); + multipathImplA.queryPathEnvelope2D(ipath_a, env_a); - if (!env.isIntersecting(envInter)) { - continue; - } + if (!env_a.isIntersecting(envInter)) + continue; - b_found_red = true; - intersector.addRedEnvelope(ipath_a, env); - } else { - multipathImplA.queryPathEnvelope2D(ipath_a, env_a); + b_found_red = true; + intersector.addRedEnvelope(ipath_a, env_a); + } + intersector.endRedConstruction(); - if (!env_a.isIntersecting(envInter)) { - continue; - } + if (!b_found_red) + return null; - b_found_red = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_a); - intersector.addRedEnvelope(ipath_a, env); - } - } - intersector.endRedConstruction(); + boolean b_found_blue = false; + intersector.startBlueConstruction(); + for (int ipath_b = 0, npaths = multipathImplB.getPathCount(); ipath_b < npaths; ipath_b++) { + if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon && !multipathImplB.isExteriorRing(ipath_b)) + continue; - if (!b_found_red) { - return null; - } + multipathImplB.queryPathEnvelope2D(ipath_b, env_b); - GeometryAccelerators accel_b = multipathImplB._getAccelerators(); - ArrayList path_envelopes_b = null; + if (!env_b.isIntersecting(envInter)) + continue; - if (accel_b != null) { - path_envelopes_b = accel_b.getPathEnvelopes(); - } + b_found_blue = true; + intersector.addBlueEnvelope(ipath_b, env_b); + } + intersector.endBlueConstruction(); - boolean b_found_blue = false; - intersector.startBlueConstruction(); - for (int ipath_b = 0; ipath_b < multipathImplB.getPathCount(); ipath_b++) { - if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon - && !multipathImplB.isExteriorRing(ipath_b)) { - continue; - } + if (!b_found_blue) + return null; - if (path_envelopes_b != null) { - Envelope2D env = path_envelopes_b.get(ipath_b); + return intersector; + } - if (!env.isIntersecting(envInter)) { - continue; - } + static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { + return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; + } + + static QuadTree buildQuadTreeForOnePath(MultiPathImpl multipathImpl, int path) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLoosePathEnvelope2D(path, extent); + QuadTree quad_tree = new QuadTree(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); - b_found_blue = true; - intersector.addBlueEnvelope(ipath_b, env); - } else { - multipathImplB.queryPathEnvelope2D(ipath_b, env_b); + seg_iter.resetToPath(path); + if (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryLooseEnvelope2D(boundingbox); + hint_index = quad_tree.insert(index, boundingbox, hint_index); - if (!env_b.isIntersecting(envInter)) { - continue; + if (hint_index == -1) { + throw new GeometryException("internal error"); } - - b_found_blue = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_b); - intersector.addBlueEnvelope(ipath_b, env); } } - intersector.endBlueConstruction(); - if (!b_found_blue) { - return null; - } - - return intersector; - } - - static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { - return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; + return quad_tree; } + } + diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index 135b8463..6947f5a1 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -25,374 +25,374 @@ final class JsonStringWriter extends JsonWriter { - @Override - Object getJson() { - next_(Action.accept); - return m_jsonString.toString(); - } - - @Override - void startObject() { - next_(Action.addContainer); - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - @Override - void startArray() { - next_(Action.addContainer); - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - @Override - void endObject() { - next_(Action.popObject); - m_jsonString.append('}'); - } - - @Override - void endArray() { - next_(Action.popArray); - m_jsonString.append(']'); - } - - @Override - void addPairObject(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueObject_(); - } - - @Override - void addPairArray(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueArray_(); - } - - @Override - void addPairString(String fieldName, String v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueString_(v); - } - - @Override - void addPairDouble(String fieldName, double v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDouble_(v); - } - - @Override - void addPairDoubleF(String fieldName, double v, int decimals) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDoubleF_(v, decimals); - } - - @Override - void addPairInt(String fieldName, int v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueInt_(v); - } - - @Override - void addPairBoolean(String fieldName, boolean v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueBoolean_(v); - } - - @Override - void addPairNull(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueNull_(); - } - - @Override - void addValueObject() { - next_(Action.addValue); - addValueObject_(); - } - - @Override - void addValueArray() { - next_(Action.addValue); - addValueArray_(); - } - - @Override - void addValueString(String v) { - next_(Action.addValue); - addValueString_(v); - } - - @Override - void addValueDouble(double v) { - next_(Action.addValue); - addValueDouble_(v); - } - - @Override - void addValueDoubleF(double v, int decimals) { - next_(Action.addValue); - addValueDoubleF_(v, decimals); - } - - @Override - void addValueInt(int v) { - next_(Action.addValue); - addValueInt_(v); - } - - @Override - void addValueBoolean(boolean v) { - next_(Action.addValue); - addValueBoolean_(v); - } - - @Override - void addValueNull() { - next_(Action.addValue); - addValueNull_(); - } - - JsonStringWriter() { - m_jsonString = new StringBuilder(); - m_functionStack = new AttributeStreamOfInt32(0); - m_functionStack.add(State.accept); - m_functionStack.add(State.start); - } - private StringBuilder m_jsonString; - private AttributeStreamOfInt32 m_functionStack; - - private void addValueObject_() { - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - private void addValueArray_() { - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - private void addValueString_(String v) { - appendQuote_(v); - } - - private void addValueDouble_(double v) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDouble(v, 17, m_jsonString); - } - - private void addValueDoubleF_(double v, int decimals) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDoubleF(v, decimals, m_jsonString); - } - - private void addValueInt_(int v) { - m_jsonString.append(v); - } - - private void addValueBoolean_(boolean v) { - if (v) { - m_jsonString.append("true"); - } else { - m_jsonString.append("false"); - } - } - - private void addValueNull_() { - m_jsonString.append("null"); - } - - private void next_(int action) { - switch (m_functionStack.getLast()) { - case State.accept: - accept_(action); - break; - case State.start: - start_(action); - break; - case State.objectStart: - objectStart_(action); - break; - case State.arrayStart: - arrayStart_(action); - break; - case State.pairEnd: - pairEnd_(action); - break; - case State.elementEnd: - elementEnd_(action); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - private void accept_(int action) { - if (action != Action.accept) { - throw new GeometryException("invalid call"); - } - } - - private void start_(int action) { - if (action == Action.addContainer) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void objectStart_(int action) { - m_functionStack.removeLast(); - - if (action == Action.addPair) { - m_functionStack.add(State.pairEnd); - } else if (action != Action.popObject) { - throw new GeometryException("invalid call"); - } - } - - private void pairEnd_(int action) { - if (action == Action.addPair) { - m_jsonString.append(','); - } else if (action == Action.popObject) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void arrayStart_(int action) { - m_functionStack.removeLast(); - - if (action == Action.addValue) { - m_functionStack.add(State.elementEnd); - } else if (action != Action.popArray) { - throw new GeometryException("invalid call"); - } - } - - private void elementEnd_(int action) { - if (action == Action.addValue) { - m_jsonString.append(','); - } else if (action == Action.popArray) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void appendQuote_(String string) { - int count = 0; - int start = 0; - int end = string.length(); - - m_jsonString.append('"'); - - for (int i = 0; i < end; i++) { - switch (string.charAt(i)) { - case '"': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\""); - start = i + 1; - break; - case '\\': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\\"); - start = i + 1; - break; - case '/': - if (i > 0 && string.charAt(i - 1) == '<') { - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\/"); - start = i + 1; - } else { - count++; - } - break; - case '\b': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\b"); - start = i + 1; - break; - case '\f': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\f"); - start = i + 1; - break; - case '\n': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\n"); - start = i + 1; - break; - case '\r': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\r"); - start = i + 1; - break; - case '\t': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\t"); - start = i + 1; - break; - default: - count++; - break; - } - } - - if (count > 0) { - m_jsonString.append(string, start, start + count); - } - - m_jsonString.append('"'); - } + @Override + Object getJson() { + next_(Action.accept); + return m_jsonString.toString(); + } + + @Override + void startObject() { + next_(Action.addContainer); + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + @Override + void startArray() { + next_(Action.addContainer); + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + @Override + void endObject() { + next_(Action.popObject); + m_jsonString.append('}'); + } + + @Override + void endArray() { + next_(Action.popArray); + m_jsonString.append(']'); + } + + @Override + void addPairObject(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueObject_(); + } + + @Override + void addPairArray(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueArray_(); + } + + @Override + void addPairString(String fieldName, String v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueString_(v); + } + + @Override + void addPairDouble(String fieldName, double v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDouble_(v); + } + + @Override + void addPairDoubleF(String fieldName, double v, int decimals) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDoubleF_(v, decimals); + } + + @Override + void addPairInt(String fieldName, int v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueInt_(v); + } + + @Override + void addPairBoolean(String fieldName, boolean v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueBoolean_(v); + } + + @Override + void addPairNull(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueNull_(); + } + + @Override + void addValueObject() { + next_(Action.addContainer); + addValueObject_(); + } + + @Override + void addValueArray() { + next_(Action.addContainer); + addValueArray_(); + } + + @Override + void addValueString(String v) { + next_(Action.addTerminal); + addValueString_(v); + } + + @Override + void addValueDouble(double v) { + next_(Action.addTerminal); + addValueDouble_(v); + } + + @Override + void addValueDoubleF(double v, int decimals) { + next_(Action.addTerminal); + addValueDoubleF_(v, decimals); + } + + @Override + void addValueInt(int v) { + next_(Action.addTerminal); + addValueInt_(v); + } + + @Override + void addValueBoolean(boolean v) { + next_(Action.addTerminal); + addValueBoolean_(v); + } + + @Override + void addValueNull() { + next_(Action.addTerminal); + addValueNull_(); + } + + JsonStringWriter() { + m_jsonString = new StringBuilder(); + m_functionStack = new AttributeStreamOfInt32(0); + m_functionStack.add(State.accept); + m_functionStack.add(State.start); + } + + private StringBuilder m_jsonString; + private AttributeStreamOfInt32 m_functionStack; + + private void addValueObject_() { + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + private void addValueArray_() { + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + private void addValueString_(String v) { + appendQuote_(v); + } + + private void addValueDouble_(double v) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDouble(v, 17, m_jsonString); + } + + private void addValueDoubleF_(double v, int decimals) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDoubleF(v, decimals, m_jsonString); + } + + private void addValueInt_(int v) { + m_jsonString.append(v); + } + + private void addValueBoolean_(boolean v) { + if (v) { + m_jsonString.append("true"); + } else { + m_jsonString.append("false"); + } + } + + private void addValueNull_() { + m_jsonString.append("null"); + } + + private void next_(int action) { + switch (m_functionStack.getLast()) { + case State.accept: + accept_(action); + break; + case State.start: + start_(action); + break; + case State.objectStart: + objectStart_(action); + break; + case State.arrayStart: + arrayStart_(action); + break; + case State.pairEnd: + pairEnd_(action); + break; + case State.elementEnd: + elementEnd_(action); + break; + default: + throw new GeometryException("internal error"); + } + } + + private void accept_(int action) { + if (action != Action.accept) { + throw new GeometryException("invalid call"); + } + } + + private void start_(int action) { + if (action == Action.addContainer) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void objectStart_(int action) { + m_functionStack.removeLast(); + + if (action == Action.addPair) { + m_functionStack.add(State.pairEnd); + } else if (action != Action.popObject) { + throw new GeometryException("invalid call"); + } + } + + private void pairEnd_(int action) { + if (action == Action.addPair) { + m_jsonString.append(','); + } else if (action == Action.popObject) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void arrayStart_(int action) { + m_functionStack.removeLast(); + + if ((action & Action.addValue) != 0) { + m_functionStack.add(State.elementEnd); + } else if (action != Action.popArray) { + throw new GeometryException("invalid call"); + } + } + + private void elementEnd_(int action) { + if ((action & Action.addValue) != 0) { + m_jsonString.append(','); + } else if (action == Action.popArray) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void appendQuote_(String string) { + int count = 0; + int start = 0; + int end = string.length(); + + m_jsonString.append('"'); + + for (int i = 0; i < end; i++) { + switch (string.charAt(i)) { + case '"': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\""); + start = i + 1; + break; + case '\\': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\\"); + start = i + 1; + break; + case '/': + if (i > 0 && string.charAt(i - 1) == '<') { + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\/"); + start = i + 1; + } else { + count++; + } + break; + case '\b': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\b"); + start = i + 1; + break; + case '\f': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\f"); + start = i + 1; + break; + case '\n': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\n"); + start = i + 1; + break; + case '\r': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\r"); + start = i + 1; + break; + case '\t': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\t"); + start = i + 1; + break; + default: + count++; + break; + } + } + + if (count > 0) { + m_jsonString.append(string, start, start + count); + } + + m_jsonString.append('"'); + } } - diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 28e15874..9e71beb8 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -25,66 +25,66 @@ abstract class JsonWriter { - abstract Object getJson(); + abstract Object getJson(); - abstract void startObject(); + abstract void startObject(); - abstract void startArray(); + abstract void startArray(); - abstract void endObject(); + abstract void endObject(); - abstract void endArray(); + abstract void endArray(); - abstract void addPairObject(String fieldName); + abstract void addPairObject(String fieldName); - abstract void addPairArray(String fieldName); + abstract void addPairArray(String fieldName); - abstract void addPairString(String fieldName, String v); + abstract void addPairString(String fieldName, String v); - abstract void addPairDouble(String fieldName, double v); + abstract void addPairDouble(String fieldName, double v); - abstract void addPairDoubleF(String fieldName, double v, int decimals); + abstract void addPairDoubleF(String fieldName, double v, int decimals); - abstract void addPairInt(String fieldName, int v); + abstract void addPairInt(String fieldName, int v); - abstract void addPairBoolean(String fieldName, boolean v); + abstract void addPairBoolean(String fieldName, boolean v); - abstract void addPairNull(String fieldName); + abstract void addPairNull(String fieldName); - abstract void addValueObject(); + abstract void addValueObject(); - abstract void addValueArray(); + abstract void addValueArray(); - abstract void addValueString(String v); + abstract void addValueString(String v); - abstract void addValueDouble(double v); + abstract void addValueDouble(double v); - abstract void addValueDoubleF(double v, int decimals); + abstract void addValueDoubleF(double v, int decimals); - abstract void addValueInt(int v); + abstract void addValueInt(int v); - abstract void addValueBoolean(boolean v); + abstract void addValueBoolean(boolean v); - abstract void addValueNull(); + abstract void addValueNull(); - protected interface Action { + protected interface Action { - static final int accept = 0; - static final int addContainer = 1; - static final int popObject = 4; - static final int popArray = 8; - static final int addPair = 16; - static final int addValue = 32; - } + static final int accept = 0; + static final int addContainer = 1; + static final int popObject = 4; + static final int popArray = 8; + static final int addPair = 16; + static final int addTerminal = 32; + static final int addValue = addContainer | addTerminal; + } - protected interface State { + protected interface State { - static final int accept = 0; - static final int start = 1; - static final int objectStart = 2; - static final int arrayStart = 3; - static final int pairEnd = 4; - static final int elementEnd = 6; - } + static final int accept = 0; + static final int start = 1; + static final int objectStart = 2; + static final int arrayStart = 3; + static final int pairEnd = 4; + static final int elementEnd = 6; + } } - diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 881cdcef..95126e81 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -35,13 +35,10 @@ */ public final class Line extends Segment implements Serializable { - // UPDATED PORT TO MATCH NATIVE AS OF JAN 30 2011 - private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and // GeometrySerializer - // HEADER DEF @Override public Geometry.Type getType() { return Type.Line; @@ -190,32 +187,15 @@ public Geometry createInstance() { } double getCoordX_(double t) { - // double x = m_x_end * t + (1.0 - t) * m_x_start; < 1.0 || (x >= m_xStart && x <= m_xEnd) || (x <= m_xStart && x >= m_xEnd)); - return x; + return MathUtils.lerp(m_xStart, m_xEnd, t); } double getCoordY_(double t) { // Must match query_coord_2D and vice verse // Also match get_attribute_as_dbl - double y; - if (t <= 0.5) { - y = m_yStart + (m_yEnd - m_yStart) * t; - } else { - y = m_yEnd - (m_yEnd - m_yStart) * (1.0 - t); - } - assert (t < 0 || t > 1.0 || (y >= m_yStart && y <= m_yEnd) || (y <= m_yStart && y >= m_yEnd)); - return y; + return MathUtils.lerp(m_yStart, m_yEnd, t); } @Override @@ -225,17 +205,7 @@ void getCoord2D(double t, Point2D pt) { // 2. When t == 1, get exactly End // 3. When m_x_end == m_x_start, we want m_x_start exactly // 4. When m_y_end == m_y_start, we want m_y_start exactly - if (t <= 0.5) { - pt.x = m_xStart + (m_xEnd - m_xStart) * t; - pt.y = m_yStart + (m_yEnd - m_yStart) * t; - } else { - pt.x = m_xEnd - (m_xEnd - m_xStart) * (1.0 - t); - pt.y = m_yEnd - (m_yEnd - m_yStart) * (1.0 - t); - } - // assert(t < 0 || t > 1.0 || (pt.x >= m_x_start && pt.x <= m_x_end) || - // (pt.x <= m_x_start && pt.x >= m_x_end)); - // assert(t < 0 || t > 1.0 || (pt.y >= m_y_start && pt.y <= m_y_end) || - // (pt.y <= m_y_start && pt.y >= m_y_end)); + MathUtils.lerp(m_xStart, m_yStart, m_xEnd, m_yEnd, t, pt); } @Override @@ -289,7 +259,7 @@ public double getAttributeAsDbl(double t, int semantics, int ordinate) { case VertexDescription.Interpolation.LINEAR: { double s = getStartAttributeAsDbl(semantics, ordinate); double e = getEndAttributeAsDbl(semantics, ordinate); - return s * (1.0 - t) + e * t; + return MathUtils.lerp(s, e, t); } case VertexDescription.Interpolation.ANGULAR: { throw new GeometryException("not implemented"); @@ -1002,6 +972,25 @@ static int _intersectLineLine(Line line1, Line line2, return 1; } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = _getAttributeAsDbl(0, semantics, i); + if (Double.isNaN(v)) + _setAttribute(0, semantics, 0, value); + + v = _getAttributeAsDbl(1, semantics, i); + if (Double.isNaN(v)) + _setAttribute(1, semantics, 0, value); + } + } + @Override int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { @@ -1014,4 +1003,13 @@ void _copyToImpl(Segment dst) { } + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String s = "Line: [" + m_xStart + ", " + m_yStart + ", " + m_xEnd + ", " + m_yEnd +"]"; + return s; + } + } diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index e28cd631..fc1ef12b 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -32,7 +32,7 @@ * together. To work with a geometry object in a map it is necessary to have a * spatial reference defined for this geometry. */ -public final class MapGeometry implements Serializable { +public class MapGeometry implements Serializable { private static final long serialVersionUID = 1L; Geometry m_geometry = null; diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index f6c9311a..8ed61457 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; -class MathUtils { +final class MathUtils { /** * The implementation of the Kahan summation algorithm. Use to get better * precision when adding a lot of values. @@ -154,4 +154,71 @@ static double round(double v) { static double sqr(double v) { return v * v; } + + /** + *Computes interpolation between two values, using the interpolation factor t. + *The interpolation formula is (end - start) * t + start. + *However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. + *It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. + */ + static double lerp(double start_, double end_, double t) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + double v; + if (t <= 0.5) + v = start_ + (end_ - start_) * t; + else + v = end_ - (end_ - start_) * (1.0 - t); + + assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_)); + return v; + } + + /** + *Computes interpolation between two values, using the interpolation factor t. + *The interpolation formula is (end - start) * t + start. + *However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. + *It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. + */ + static void lerp(Point2D start_, Point2D end_, double t, Point2D result) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + if (t <= 0.5) { + result.x = start_.x + (end_.x - start_.x) * t; + result.y = start_.y + (end_.y - start_.y) * t; + } + else { + result.x = end_.x - (end_.x - start_.x) * (1.0 - t); + result.y = end_.y - (end_.y - start_.y) * (1.0 - t); + } + + assert (t < 0 || t > 1.0 || (result.x >= start_.x && result.x <= end_.x) || (result.x <= start_.x && result.x >= end_.x)); + assert (t < 0 || t > 1.0 || (result.y >= start_.y && result.y <= end_.y) || (result.y <= start_.y && result.y >= end_.y)); + } + + static void lerp(double start_x, double start_y, double end_x, double end_y, double t, Point2D result) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + if (t <= 0.5) { + result.x = start_x + (end_x - start_x) * t; + result.y = start_y + (end_y - start_y) * t; + } + else { + result.x = end_x - (end_x - start_x) * (1.0 - t); + result.y = end_y - (end_y - start_y) * (1.0 - t); + } + + assert (t < 0 || t > 1.0 || (result.x >= start_x && result.x <= end_x) || (result.x <= start_x && result.x >= end_x)); + assert (t < 0 || t > 1.0 || (result.y >= start_y && result.y <= end_y) || (result.y <= start_y && result.y >= end_y)); + } + } diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 3dbda88d..1fb76d68 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -738,4 +738,9 @@ public int getStateFlag() { return m_impl.getStateFlag(); } + @Override + public void replaceNaNs(int semantics, double value) { + m_impl.replaceNaNs(semantics, value); + } + } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 33e530b6..ba566436 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -54,6 +54,7 @@ final class MultiPathImpl extends MultiVertexGeometryImpl { protected AttributeStreamOfDbl m_segmentParams; protected int m_curveParamwritePoint; private int m_currentPathIndex; + private int m_fill_rule = Polygon.FillRule.enumFillRuleOddEven; static int[] _segmentParamSizes = { 0, 0, 6, 0, 8, 0 }; // None, Line, // Bezier, XXX, Arc, @@ -136,6 +137,7 @@ public void startPath(Point point) { throw new IllegalArgumentException();// throw new // IllegalArgumentException(); + mergeVertexDescription(point.getDescription()); _initPathStartPoint(); point.copyTo(m_moveToPoint); @@ -1632,8 +1634,7 @@ void interpolateAttributes_(int semantics, int from_path_index, double segment_length = segment.calculateLength2D(); cumulative_length += segment_length; double t = cumulative_length / sub_length; - interpolated_attribute = (1.0 - t) * from_attribute + t - * to_attribute; + interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); if (!seg_iter.isClosingSegment()) setAttribute(semantics, seg_iter.getEndPointIndex(), @@ -1676,8 +1677,7 @@ void interpolateAttributes_(int semantics, int path_index, double segment_length = segment.calculateLength2D(); cumulative_length += segment_length; double t = cumulative_length / sub_length; - prev_interpolated_attribute = (1.0 - t) * from_attribute + t - * to_attribute; + prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); } while (seg_iter.getEndPointIndex() != absolute_to_index); } @@ -1846,7 +1846,8 @@ void _copyToImpl(MultiVertexGeometryImpl dst) { MultiPathImpl dstPoly = (MultiPathImpl) dst; dstPoly.m_bPathStarted = false; dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; - + dstPoly.m_fill_rule = m_fill_rule; + if (m_paths != null) dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); else @@ -1925,6 +1926,9 @@ public boolean equals(Object other) { if (m_paths != null && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) return false; + + if (m_fill_rule != otherMultiPath.m_fill_rule) + return false; if (m_pathFlags != null && !m_pathFlags @@ -2508,6 +2512,30 @@ void queryPathEnvelope2D(int path_index, Envelope2D envelope) { } } + public void queryLoosePathEnvelope2D(int path_index, Envelope2D envelope) { + if (path_index >= getPathCount()) + throw new IllegalArgumentException(); + + if (isEmpty()) { + envelope.setEmpty(); + return; + } + + if (hasNonLinearSegments(path_index)) { + throw new GeometryException("not implemented"); + } else { + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + Point2D pt = new Point2D(); + Envelope2D env = new Envelope2D(); + env.setEmpty(); + for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + envelope.setCoords(env); + } + } + @Override public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { if (m_accelerators == null)// (!m_accelerators) @@ -2524,22 +2552,30 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { return true; } - boolean _buildPathEnvelopesAccelerator(GeometryAccelerationDegree d) { - if (m_accelerators == null) { - m_accelerators = new GeometryAccelerators(); - } + boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } - ArrayList path_envelopes = new ArrayList(0); + //TODO: when less than two envelopes - no need to this. - for (int ipath = 0; ipath < getPathCount(); ipath++) { - Envelope2D env = new Envelope2D(); - queryPathEnvelope2D(ipath, env); - path_envelopes.add(env); - } + if (m_accelerators.getQuadTreeForPaths() != null) + return true; - m_accelerators._setPathEnvelopes(path_envelopes); + m_accelerators._setQuadTreeForPaths(null); + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); + m_accelerators._setQuadTreeForPaths(quad_tree_impl); + + return true; + } + + void setFillRule(int rule) { + assert(m_bPolygon); + m_fill_rule = rule; + } + int getFillRule() { + return m_fill_rule; + } - return true; - } } diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index e890cb8a..3604e108 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -364,4 +364,9 @@ public int getStateFlag() { public Geometry getBoundary() { return m_impl.getBoundary(); } + + @Override + public void replaceNaNs(int semantics, double value) { + m_impl.replaceNaNs(semantics, value); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index fc614cfb..6fb511ea 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -972,7 +972,7 @@ void _interpolateTwoVertices(int vertex1, int vertex2, double f, * vertex1 + icomp); double v2 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp * vertex2 + icomp); - outPoint.setAttribute(semantics, icomp, v1 * (1.0 - f) + v2 * f); + outPoint.setAttribute(semantics, icomp, MathUtils.lerp(v1, v2, f)); } } } @@ -1068,6 +1068,43 @@ public void queryCoordinates(Point3D[] dst) { dst[i] = getXYZ(i); } } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + boolean modified = false; + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + int attr = m_description.getAttributeIndex(semantics); + AttributeStreamBase streamBase = getAttributeStreamRef(attr); + if (streamBase instanceof AttributeStreamOfDbl) { + AttributeStreamOfDbl dblStream = (AttributeStreamOfDbl)streamBase; + for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { + double v = dblStream.read(ivert); + if (Double.isNaN(v)) { + dblStream.write(ivert, value); + modified = true; + } + } + } + else { + for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { + double v = streamBase.readAsDbl(ivert); + if (Double.isNaN(v)) { + streamBase.writeAsDbl(ivert, value); + modified = true; + } + } + } + } + + if (modified) { + notifyModified(DirtyFlags.DirtyCoordinates); + } + } public abstract boolean _buildRasterizedGeometryAccelerator( double toleranceXY, GeometryAccelerationDegree accelDegree); diff --git a/src/main/java/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java index b8d9d029..ac79c687 100644 --- a/src/main/java/com/esri/core/geometry/NonSimpleResult.java +++ b/src/main/java/com/esri/core/geometry/NonSimpleResult.java @@ -23,37 +23,65 @@ */ package com.esri.core.geometry; -class NonSimpleResult { +/** + * The result of the IsSimpleXXX. + * + * + */ +public class NonSimpleResult { public enum Reason { - NotDetermined, // = 1; + boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; + m_intersector = InternalUtils.getEnvelope2DIntersectorForParts(multi_path_impl_a, multi_path_impl_b, tolerance, b_simple_a, b_simple_b); + } + } + } + + boolean next() { + if (m_b_quad_tree) { + if (m_b_done) + return false; + + boolean b_searching = true; + while (b_searching) { + switch (m_function) { + case State.nextPath: + b_searching = nextPath_(); + break; + case State.nextSegment: + b_searching = nextSegment_(); + break; + case State.iterate: + b_searching = iterate_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + if (m_b_done) + return false; + + return true; + } + + if (m_intersector == null) + return false; + + return m_intersector.next(); + } + + int getRedElement() { + if (m_b_quad_tree) { + if (!m_b_swap_elements) + return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getRedElement(m_intersector.getHandleA()); + } + + int getBlueElement() { + if (m_b_quad_tree) { + if (m_b_swap_elements) + return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getBlueElement(m_intersector.getHandleB()); + } + + Envelope2D getRedEnvelope() { + if (!m_b_paths) + throw GeometryException.GeometryInternalError(); + + if (m_b_quad_tree) { + if (!m_b_swap_elements) + return m_paths_query; + + return m_quad_tree.getElementExtent(m_element_handle); + } + + return m_intersector.getRedEnvelope(m_intersector.getHandleA()); + } + + Envelope2D getBlueEnvelope() { + if (!m_b_paths) + throw GeometryException.GeometryInternalError(); + + if (m_b_quad_tree) { + if (m_b_swap_elements) + return m_paths_query; + + return m_quad_tree.getElementExtent(m_element_handle); + } + + return m_intersector.getBlueEnvelope(m_intersector.getHandleB()); + } + + boolean nextPath_() { + if (!m_b_paths) { + if (!m_seg_iter.nextPath()) { + m_b_done = true; + return false; + } + + m_function = State.nextSegment; + return true; + } + + if (--m_path_index == -1) { + m_b_done = true; + return false; + } + + if (m_b_swap_elements) + m_multi_path_impl_b.queryPathEnvelope2D(m_path_index, m_paths_query); + else + m_multi_path_impl_a.queryPathEnvelope2D(m_path_index, m_paths_query); + + m_qt_iter.resetIterator(m_paths_query, m_tolerance); + m_function = State.iterate; + return true; + } + + boolean nextSegment_() { + if (!m_seg_iter.hasNextSegment()) { + m_function = State.nextPath; + return true; + } + + Segment segment = m_seg_iter.nextSegment(); + m_qt_iter.resetIterator(segment, m_tolerance); + m_function = State.iterate; + return true; + } + + boolean iterate_() { + m_element_handle = m_qt_iter.next(); + + if (m_element_handle == -1) { + m_function = (!m_b_paths ? State.nextSegment : State.nextPath); + return true; + } + + return false; + } +} diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index ab23f7c1..41db31be 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -68,7 +68,11 @@ boolean sweep(EditShape shape, double tolerance) { b_cracked |= sweepImpl_(); } - m_shape.removeUserIndex(m_vertex_cluster_index); + if (m_vertex_cluster_index != -1) { + m_shape.removeUserIndex(m_vertex_cluster_index); + m_vertex_cluster_index = -1; + } + m_shape = null; return m_b_cracked; } @@ -82,9 +86,17 @@ boolean sweepVertical(EditShape shape, double tolerance) { m_complications = false; boolean bresult = sweepImpl_(); if (!m_complications) { - int filtered = shape.filterClosePoints(tolerance, true); + int filtered = shape.filterClosePoints(tolerance, true, false); m_complications = filtered == 1; + bresult |= filtered == 1; + } + + if (m_vertex_cluster_index != -1) { + m_shape.removeUserIndex(m_vertex_cluster_index); + m_vertex_cluster_index = -1; } + + m_shape = null; return bresult; } @@ -758,22 +770,31 @@ int compare(Treap treap, int node) { void processSplitHelper1_(int index, int edge, SegmentIntersector intersector) { + int clusterStart = getEdgeCluster(edge, 0); + Point2D ptClusterStart = new Point2D(); + getClusterXY(clusterStart, ptClusterStart); + Point2D ptClusterEnd = new Point2D(); + int clusterEnd = getEdgeCluster(edge, 1); + getClusterXY(clusterEnd, ptClusterEnd); + // Collect all edges that are affected by the split and that are in the // sweep structure. int count = intersector.getResultSegmentCount(index); Segment seg = intersector.getResultSegment(index, 0); - seg.getStartXY(pt_2); - int clusterStart = getEdgeCluster(edge, 0); - getClusterXY(clusterStart, pt_1); - if (!pt_1.isEqual(pt_2)) { - int res1 = pt_1.compare(m_sweep_point); - int res2 = pt_2.compare(m_sweep_point); - if (res1 * res2 < 0) { - m_complications = true;// point is not yet have been processed - // but moved before the sweep point, - // this will require - // repeating the cracking step and the sweep_vertical cannot - // help here + Point2D newStart = new Point2D(); + seg.getStartXY(newStart); + + if (!ptClusterStart.isEqual(newStart)) { + if (!m_complications) { + int res1 = ptClusterStart.compare(m_sweep_point); + int res2 = newStart.compare(m_sweep_point); + if (res1 * res2 < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point, + // this will require + // repeating the cracking step and the sweep_vertical cannot + // help here + } } // This cluster's position needs to be changed @@ -781,18 +802,37 @@ void processSplitHelper1_(int index, int edge, m_modified_clusters.add(clusterStart); } - seg = intersector.getResultSegment(index, count - 1); - seg.getEndXY(pt_2); - int clusterEnd = getEdgeCluster(edge, 1); - getClusterXY(clusterEnd, pt_1); - if (!pt_1.isEqual(pt_2)) { - int res1 = pt_1.compare(m_sweep_point); - int res2 = pt_2.compare(m_sweep_point); - if (res1 * res2 < 0) { - m_complications = true;// point is not yet have been processed - // but moved before the sweep point. + if (!m_complications && count > 1) { + int dir = ptClusterStart.compare(ptClusterEnd); + Point2D midPoint = seg.getEndXY(); + if (ptClusterStart.compare(midPoint) != dir + || midPoint.compare(ptClusterEnd) != dir) {// split segment + // midpoint is + // above the + // sweep line. + // Therefore the + // part of the + // segment + m_complications = true; + } else { + if (midPoint.compare(m_sweep_point) < 0) { + // midpoint moved below sweepline. + m_complications = true; + } } + } + seg = intersector.getResultSegment(index, count - 1); + Point2D newEnd = seg.getEndXY(); + if (!ptClusterEnd.isEqual(newEnd)) { + if (!m_complications) { + int res1 = ptClusterEnd.compare(m_sweep_point); + int res2 = newEnd.compare(m_sweep_point); + if (res1 * res2 < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point. + } + } // This cluster's position needs to be changed getAffectedEdges(clusterEnd, m_temp_edge_buffer); m_modified_clusters.add(clusterEnd); diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 11459be5..9bbf20a4 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -623,4 +623,18 @@ public int hashCode() { public Geometry getBoundary() { return null; } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = getAttributeAsDbl(semantics, i); + if (Double.isNaN(v)) + setAttribute(semantics, i, value); + } + } } diff --git a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java index 0cda4401..8664ed8d 100644 --- a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; -class PointInPolygonHelper { +final class PointInPolygonHelper { private Point2D m_inputPoint; private int m_windnum; @@ -164,7 +164,7 @@ private boolean processSegment(Segment segment) { private static int _isPointInPolygonInternal(Polygon inputPolygon, Point2D inputPoint, double tolerance) { - boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, inputPoint, tolerance); MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); @@ -187,7 +187,7 @@ private static int _isPointInPolygonInternalWithQuadTree( inputPolygon.queryLooseEnvelope(envPoly); envPoly.inflate(tolerance, tolerance); - boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, inputPoint, tolerance); @@ -229,7 +229,7 @@ public static int isPointInPolygon(Polygon inputPolygon, MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); GeometryAccelerators accel = mpImpl._getAccelerators(); if (accel != null) { - //geometry has spatial indices built. Try using them. + // geometry has spatial indices built. Try using them. RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); if (rgeom != null) { RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( @@ -241,7 +241,7 @@ else if (hit == RasterizedGeometry2D.HitType.Outside) } QuadTreeImpl qtree = accel.getQuadTree(); - if (qtree != null) { + if (qtree != null) { return _isPointInPolygonInternalWithQuadTree(inputPolygon, qtree, inputPoint, tolerance); } @@ -280,28 +280,61 @@ else if (hit == RasterizedGeometry2D.HitType.Outside) } public static int isPointInRing(MultiPathImpl inputPolygonImpl, int iRing, - Point2D inputPoint, double tolerance) { + Point2D inputPoint, double tolerance, QuadTree quadTree) { Envelope2D env = new Envelope2D(); inputPolygonImpl.queryLooseEnvelope2D(env); env.inflate(tolerance, tolerance); if (!env.contains(inputPoint)) return 0; - boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + boolean bAltenate = true; PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, inputPoint, tolerance); - SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); - iter.resetToPath(iRing); - if (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary + if (quadTree != null) { + Envelope2D queryEnv = new Envelope2D(); + queryEnv.setCoords(env); + queryEnv.xmax = inputPoint.x + tolerance;// no need to query + // segments to + // the right of the + // point. + // Only segments to the + // left + // matter. + queryEnv.ymin = inputPoint.y - tolerance; + queryEnv.ymax = inputPoint.y + tolerance; + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + QuadTree.QuadTreeIterator qiter = quadTree.getIterator(queryEnv, + tolerance); + + for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter + .next()) { + iter.resetToVertex(quadTree.getElement(qhandle), iRing); + if (iter.hasNextSegment()) { + if (iter.getPathIndex() != iRing) + continue; + + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } } - } - return helper.result(); + return helper.result(); + } else { + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + iter.resetToPath(iRing); + + if (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } } public static int isPointInPolygon(Polygon inputPolygon, Point inputPoint, @@ -354,4 +387,50 @@ public static int isPointInAnyOuterRing(Polygon inputPolygon, return helper.result(); } + // Tests if Ring1 is inside Ring2. + // We assume here that the Polygon is Weak Simple. That is if one point of + // Ring1 is found to be inside of Ring2, then + // we assume that all of Ring1 is inside Ring2. + static boolean _isRingInRing2D(MultiPath polygon, int iRing1, int iRing2, + double tolerance, QuadTree quadTree) { + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); + segIter.resetToPath(iRing1); + if (!segIter.nextPath() || !segIter.hasNextSegment()) + throw new GeometryException("corrupted geometry"); + + int res = 2; + + while (res == 2 && segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + Point2D point = segment.getCoord2D(0.5); + res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, + point, tolerance, quadTree); + } + + if (res == 2) + throw GeometryException.GeometryInternalError(); + if (res == 1) + return true; + + return false; + } + + static boolean quadTreeWillHelp(Polygon polygon, int c_queries) + { + int n = polygon.getPointCount(); + + if (n < 16) + return false; + + double c_build_quad_tree = 2.0; // what's a good constant? + double c_query_quad_tree = 1.0; // what's a good constant? + double c_point_in_polygon_brute_force = 1.0; // what's a good constant? + + double c_quad_tree = c_build_quad_tree * n + c_query_quad_tree * (Math.log((double)n) / Math.log(2.0)) * c_queries; + double c_brute_force = c_point_in_polygon_brute_force * n * c_queries; + + return c_quad_tree < c_brute_force; + } + } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 8faea700..0c07f6f4 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -138,5 +138,38 @@ public void interpolateAttributes(int semantics, int path_index, public int getExteriorRingCount() { return m_impl.getOGCPolygonCount(); } - + + public interface FillRule { + /** + * odd-even fill rule. This is the default value. A point is in the polygon interior if a ray + * from this point to infinity crosses odd number of segments of the polygon. + */ + public final static int enumFillRuleOddEven = 0; + /** + * winding fill rule (aka non-zero winding rule). A point is in the polygon interior if a winding number is not zero. + * To compute a winding number for a point, draw a ray from this point to infinity. If N is the number of times the ray + * crosses segments directed up and the M is the number of times it crosses segments directed down, + * then the winding number is equal to N-M. + */ + public final static int enumFillRuleWinding = 1; + }; + + /** + *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. + *Can be use by drawing code to pass around the fill rule of graphic path. + *This property is not persisted in any format yet. + */ + public void setFillRule(int rule) { + m_impl.setFillRule(rule); + } + + /** + *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. + *Changing the fill rule on the polygon that has no self intersections has no physical effect. + *Can be use by drawing code to pass around the fill rule of graphic path. + *This property is not persisted in any format yet. + */ + public int getFillRule() { + return m_impl.getFillRule(); + } } diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index 005446d0..9a6b52f1 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; -class PolygonUtils { +final class PolygonUtils { enum PiPResult { PiPOutside, PiPInside, PiPBoundary @@ -89,7 +89,7 @@ public static PiPResult isPointInRing2D(Polygon polygon, int iRing, Point2D inputPoint, double tolerance) { MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); int res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing, - inputPoint, tolerance); + inputPoint, tolerance, null); if (res == 0) return PiPResult.PiPOutside; if (res == 1) @@ -242,36 +242,6 @@ else if (polygon.getType() == Geometry.Type.Envelope) { throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); } - // Tests if Ring1 is inside Ring2. - // We assume here that the Polygon is Weak Simple. That is if one point of - // Ring1 is found to be inside of Ring2, then - // we assume that all of Ring1 is inside Ring2. - static boolean _isRingInRing2D(MultiPath polygon, int iRing1, - int iRing2, double tolerance) { - MultiPathImpl polygonImpl = (MultiPathImpl)polygon._getImpl(); - SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); - segIter.resetToPath(iRing1); - if (!segIter.nextPath() || !segIter.hasNextSegment()) - throw new GeometryException("corrupted geometry"); - - int res = 2;// 2(int)PiPResult.PiPBoundary; - - while (res == 2 /* (int)PiPResult.PiPBoundary */ - && segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - Point2D point = segment.getCoord2D(0.5); - res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, - point, tolerance); - } - - if (res == 2 /* (int)PiPResult.PiPBoundary */) - throw GeometryException.GeometryInternalError(); - if (res == 1 /* (int)PiPResult.PiPInside */) - return true; - - return false; - } - private static void _testPointsInEnvelope2D(Envelope2D env2D, Point2D[] inputPoints, int count, double tolerance, PiPResult[] testResults) { diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 2124a0f4..1b648f6c 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -48,6 +48,15 @@ public Polyline() { m_impl = new MultiPathImpl(false, vd); } + /** + * Creates a polyline with one line segment. + */ + public Polyline(Point start, Point end) { + m_impl = new MultiPathImpl(false, start.getDescription()); + startPath(start); + lineTo(end); + } + @Override public Geometry createInstance() { return new Polyline(getDescription()); diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 84c16f67..3b9afd77 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -345,13 +345,26 @@ void removeElement(int element_handle) { } /** - * Returns the element at the given element_handle. \param element_handle + * Returns the element at the given element_handle. + * \param element_handle * The handle corresponding to the element to be retrieved. */ int getElement(int element_handle) { return getElement_(element_handle); } + + /** + * Returns a reference to the element extent at the given element_handle. + * \param element_handle + * The handle corresponding to the element to be retrieved. + */ + Envelope2D getElementExtent(int element_handle) + { + int box_handle = getBoxHandle_(element_handle); + return getBoundingBox_(box_handle); + } + /** * Returns the height of the quad at the given quad_handle. \param * quad_handle The handle corresponding to the quad. diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 08d60ca3..0658a8e5 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1139,32 +1139,49 @@ private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, if (relation == Relation.disjoint || relation == Relation.contains) return false; - Envelope2D env_a_inflated = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a_inflated); - env_a_inflated.inflate(tolerance, tolerance); + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); - Point2D ptB = new Point2D(); - boolean b_boundary = false; + Point2D ptB; + boolean b_boundary = false; - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); + MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); - if (!env_a_inflated.contains(ptB)) - continue; + Polygon pa = null; + Polygon p_polygon_a = null; - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, ptB, tolerance); + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } - if (result == PolygonUtils.PiPResult.PiPBoundary) - b_boundary = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - return false; - } + for (int i = 0; i < multipoint_b.getPointCount(); i++) + { + ptB = multipoint_b.getXY(i); - if (b_boundary) - return true; + if (!env_a_inflated.contains(ptB)) + continue; - return false; + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPBoundary) + b_boundary = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + return false; + } + + if (b_boundary) + return true; + + return false; } // Returns true if polygon_a crosses multipoint_b. @@ -1179,36 +1196,56 @@ private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, if (relation == Relation.disjoint || relation == Relation.contains) return false; - Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a_inflated.setCoords(env_a); - env_a_inflated.inflate(tolerance, tolerance); - - boolean b_interior = false, b_exterior = false; - - Point2D pt_b = new Point2D(); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, pt_b); - - if (!env_a_inflated.contains(pt_b)) { - b_exterior = true; - } else { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D(polygon_a, pt_b, tolerance); - - if (result == PolygonUtils.PiPResult.PiPOutside) - b_exterior = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - b_interior = true; - } - - if (b_interior && b_exterior) - return true; - } - - return false; + Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance, tolerance); + + boolean b_interior = false, b_exterior = false; + + Point2D pt_b; + + MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); + + Polygon pa = null; + Polygon p_polygon_a = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } + + for (int i = 0; i < multipoint_b.getPointCount(); i++) + { + pt_b = multipoint_b.getXY(i); + + if (!env_a_inflated.contains(pt_b)) + { + b_exterior = true; + } + else + { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + b_exterior = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + } + + if (b_interior && b_exterior) + return true; + } + + return false; } // Returns true if polygon_a contains multipoint_b. @@ -1234,25 +1271,42 @@ private static boolean polygonContainsMultiPoint_(Polygon polygon_a, if (relation == Relation.contains) return true; - boolean b_interior = false; - Point2D ptB = new Point2D(); + boolean b_interior = false; + Point2D ptB; - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); + MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); - if (!env_a.contains(ptB)) - return false; + Polygon pa = null; + Polygon p_polygon_a = null; - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, ptB, tolerance); + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } - if (result == PolygonUtils.PiPResult.PiPInside) - b_interior = true; - else if (result == PolygonUtils.PiPResult.PiPOutside) - return false; - } + for (int i = 0; i < multipoint_b.getPointCount(); i++) + { + ptB = multipoint_b.getXY(i); - return b_interior; + if (!env_a.contains(ptB)) + return false; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + else if (result == PolygonUtils.PiPResult.PiPOutside) + return false; + } + + return b_interior; } // Returns true if polygon_a equals envelope_b. @@ -1540,6 +1594,14 @@ private static boolean polylineDisjointPolyline_(Polyline polyline_a, false) == Relation.disjoint) return true; + MultiPathImpl multi_path_impl_a = (MultiPathImpl)polyline_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl)polyline_b._getImpl(); + + PairwiseIntersectorImpl intersector_paths = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); + + if (!intersector_paths.next()) + return false; + return !linearPathIntersectsLinearPath_(polyline_a, polyline_b, tolerance); } @@ -1716,14 +1778,16 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl qtA; - QuadTreeImpl quadTreeA; + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) ._getAccelerators(); if (accel != null) { quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); if (quadTreeA == null) { qtA = InternalUtils.buildQuadTree( (MultiPathImpl) polyline_a._getImpl(), envInter); @@ -1737,6 +1801,10 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; double toleranceSq = tolerance * tolerance; @@ -1755,6 +1823,14 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, } env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) + continue; + } + qtIterA.resetIterator(env_b, tolerance); for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA @@ -1818,14 +1894,16 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl qtA; - QuadTreeImpl quadTreeA; + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) ._getAccelerators(); if (accel != null) { quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); if (quadTreeA == null) { qtA = InternalUtils.buildQuadTree( (MultiPathImpl) polyline_a._getImpl(), envInter); @@ -1839,7 +1917,11 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); - Point2D ptB = new Point2D(), closest = new Point2D(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; boolean b_exterior_found = false; double toleranceSq = tolerance * tolerance; @@ -1859,6 +1941,16 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, } env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) { + b_exterior_found = true; + continue; + } + } + qtIterA.resetIterator(env_b, tolerance); boolean b_covered = false; @@ -3071,71 +3163,90 @@ private static boolean envelopeCrossesEnvelope_(Envelope2D env_a, private static boolean polygonDisjointMultiPath_(Polygon polygon_a, MultiPath multipath_b, double tolerance, ProgressTracker progress_tracker) { - Point2D pt_a, pt_b; - Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); - - MultiPathImpl multi_path_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipath_b - ._getImpl(); - - boolean b_simple_a = multi_path_impl_a.getIsSimple(0.0) >= 1; - boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersectorForParts(multi_path_impl_a, - multi_path_impl_b, tolerance, b_simple_a, b_simple_b); - - if (intersector != null) { - if (!intersector.next()) { - return true; // no rings intersect - } - } else { - return true; // no rings intersect - } - - boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, - multipath_b, tolerance); - - if (b_intersects) { - return false; - } - - do { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int path_a = intersector.getRedElement(index_a); - int path_b = intersector.getBlueElement(index_b); - - pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); - env_a_inf.setCoords(intersector.getRedEnvelope(index_a)); - env_a_inf.inflate(tolerance, tolerance); - - if (env_a_inf.contains(pt_b)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D(polygon_a, pt_b, 0.0); - - if (result != PolygonUtils.PiPResult.PiPOutside) { - return false; - } - } - - if (multipath_b.getType() == Geometry.Type.Polygon) { - pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); - env_b_inf.setCoords(intersector.getBlueEnvelope(index_b)); - env_b_inf.inflate(tolerance, tolerance); - - if (env_b_inf.contains(pt_a)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D((Polygon) multipath_b, pt_a, - 0.0); - - if (result != PolygonUtils.PiPResult.PiPOutside) { - return false; - } - } - } - } while (intersector.next()); - - return true; + Point2D pt_a, pt_b; + Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); + + MultiPathImpl multi_path_impl_a = (MultiPathImpl)polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl)multipath_b._getImpl(); + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); + + if (!intersector.next()) + return true; // no rings intersect + + boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, multipath_b, tolerance); + + if (b_intersects) + return false; + + Polygon pa = null; + Polygon p_polygon_a = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPointCount()) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } + + Polygon pb = null; + Polygon p_polygon_b = null; + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + { + Polygon polygon_b = (Polygon)multipath_b; + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) + { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } + else + { + p_polygon_b = (Polygon)multipath_b; + } + } + + do + { + int path_a = intersector.getRedElement(); + int path_b = intersector.getBlueElement(); + + pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); + env_a_inf.setCoords(intersector.getRedEnvelope()); + env_a_inf.inflate(tolerance, tolerance); + + if (env_a_inf.contains(pt_b)) + { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + { + pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); + env_b_inf.setCoords(intersector.getBlueEnvelope()); + env_b_inf.inflate(tolerance, tolerance); + + if (env_b_inf.contains(pt_a)) + { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_b, pt_a, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + } + } while (intersector.next()); + + return true; } // Returns true if env_a inflated contains env_b. @@ -3448,14 +3559,16 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) .querySegmentIterator(); - QuadTreeImpl qtB; - QuadTreeImpl quadTreeB; + QuadTreeImpl qtB = null; + QuadTreeImpl quadTreeB = null; + QuadTreeImpl quadTreePathsB = null; GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) ._getAccelerators(); if (accel != null) { quadTreeB = accel.getQuadTree(); + quadTreePathsB = accel.getQuadTreeForPaths(); if (quadTreeB == null) { qtB = InternalUtils.buildQuadTree( (MultiPathImpl) multipathB._getImpl(), envInter); @@ -3469,6 +3582,10 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; + if (quadTreePathsB != null) + qtIterPathsB = quadTreePathsB.getIterator(); + while (segIterA.nextPath()) { while (segIterA.hasNextSegment()) { boolean bStringOfSegmentAsCovered = false; @@ -3480,6 +3597,15 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, return false; // bWithin = false } + if (qtIterPathsB != null) { + qtIterPathsB.resetIterator(env_a, tolerance); + + if (qtIterPathsB.next() == -1) { + bWithin = false; + return false; + } + } + double lengthA = segmentA.calculateLength2D(); qtIterB.resetIterator(segmentA, tolerance); @@ -3709,14 +3835,16 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, int_point = new Point2D(); } - QuadTreeImpl qtB; - QuadTreeImpl quadTreeB; + QuadTreeImpl qtB = null; + QuadTreeImpl quadTreeB = null; + QuadTreeImpl quadTreePathsB = null; GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) ._getAccelerators(); if (accel != null) { quadTreeB = accel.getQuadTree(); + quadTreePathsB = accel.getQuadTreeForPaths(); if (quadTreeB == null) { qtB = InternalUtils.buildQuadTree( (MultiPathImpl) multipathB._getImpl(), envInter); @@ -3730,6 +3858,10 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; + if (quadTreePathsB != null) + qtIterPathsB = quadTreePathsB.getIterator(); + while (segIterA.nextPath()) { overlapLength = 0.0; @@ -3741,6 +3873,13 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, continue; } + if (qtIterPathsB != null) { + qtIterPathsB.resetIterator(env_a, tolerance); + + if (qtIterPathsB.next() == -1) + continue; + } + double lengthA = segmentA.calculateLength2D(); qtIterB.resetIterator(segmentA, tolerance); @@ -3954,12 +4093,11 @@ private static boolean linearPathIntersectsLinearPath_( SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); - Pair_wise_intersector intersector = new Pair_wise_intersector( - multi_path_impl_a, multi_path_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, false); while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4006,6 +4144,7 @@ private static boolean linearPathIntersectsMultiPoint_( QuadTreeImpl qtA = null; QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) ._getAccelerators(); @@ -4024,6 +4163,11 @@ private static boolean linearPathIntersectsMultiPoint_( } QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; double toleranceSq = tolerance * tolerance; @@ -4035,8 +4179,15 @@ private static boolean linearPathIntersectsMultiPoint_( continue; } - boolean bPtBContained = false; env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) + continue; + } + qtIterA.resetIterator(env_b, tolerance); boolean b_covered = false; @@ -4341,14 +4492,14 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polygon_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polygon_impl_b, tolerance, false); boolean b_boundaries_intersect = false; while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4450,12 +4601,12 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polygon_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polygon_impl_b, tolerance, false); while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4552,65 +4703,174 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polygon_impl_b, tolerance); - - while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 1) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return false; - } - } - } - - // We can clip polygon_a to the extent of polyline_b - Envelope2D envBInflated = new Envelope2D(); - polygon_b.queryEnvelope2D(envBInflated); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - Polygon _polygonA; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, - tolerance, 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false - - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( - _polygonA, polygon_b, tolerance, scl, progressTracker); - return bRelation; + boolean[] b_result_known = new boolean[1]; + b_result_known[0] = false; + boolean res = polygonContainsMultiPath_(polygon_a, polygon_b, tolerance, b_result_known, progressTracker); + + if (b_result_known[0]) + return res; + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polygon_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA = null; + + if (polygon_a.getPointCount() > 10) + { + _polygonA = (Polygon)Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); + if (_polygonA.isEmpty()) + return false; + } + else + { + _polygonA = polygon_a; + } + + boolean bContains = RelationalOperationsMatrix.polygonContainsPolygon_(_polygonA, polygon_b, tolerance, progressTracker); + return bContains; } + private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath multi_path_b, double tolerance, boolean[] b_result_known, ProgressTracker progress_tracker) + { + b_result_known[0] = false; + + MultiPathImpl polygon_impl_a = (MultiPathImpl)polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl)multi_path_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(polygon_impl_a, multi_path_impl_b, tolerance, false); + boolean b_boundaries_intersect = false; + + while (intersector.next()) + { + b_boundaries_intersect = true; + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a, -1); + segIterB.resetToVertex(vertex_b, -1); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, tolerance); + + if (result == 1) + { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + { + b_result_known[0] = true; + return false; + } + } + } + + if (!b_boundaries_intersect) + { + b_result_known[0] = true; + + //boundaries do not intersect + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + + Polygon pa = null; + Polygon p_polygon_a = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPointCount()) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } + + Envelope2D path_env_b = new Envelope2D(); + + for (int ipath = 0, npath = multi_path_b.getPathCount(); ipath < npath; ipath++) + { + if (multi_path_b.getPathSize(ipath) > 0) + { + multi_path_b.queryPathEnvelope2D(ipath, path_env_b); + + if (env_a_inflated.isIntersecting(path_env_b)) + { + Point2D anyPoint = multi_path_b.getXY(multi_path_b.getPathStart(ipath)); + int res = PointInPolygonHelper.isPointInPolygon(p_polygon_a, anyPoint, 0); + if (res == 0) + return false; + } + else + { + return false; + } + } + } + + if (polygon_a.getPathCount() == 1 || multi_path_b.getType().value() == Geometry.GeometryType.Polyline) + return true; //boundaries do not intersect. all paths of b are inside of a + + // Polygon A has multiple rings, and Multi_path B is a polygon. + + Polygon polygon_b = (Polygon)multi_path_b; + + Envelope2D env_b_inflated = new Envelope2D(); + polygon_b.queryEnvelope2D(env_b_inflated); + env_b_inflated.inflate(tolerance, tolerance); + + Polygon pb = null; + Polygon p_polygon_b = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) + { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } + else + { + p_polygon_b = polygon_b; + } + + Envelope2D path_env_a = new Envelope2D(); + + for (int ipath = 0, npath = polygon_a.getPathCount(); ipath < npath; ipath++) + { + if (polygon_a.getPathSize(ipath) > 0) + { + polygon_a.queryPathEnvelope2D(ipath, path_env_a); + + if (env_b_inflated.isIntersecting(path_env_a)) + { + Point2D anyPoint = polygon_a.getXY(polygon_a.getPathStart(ipath)); + int res = PointInPolygonHelper.isPointInPolygon(p_polygon_b, anyPoint, 0); + if (res == 1) + return false; + } + } + } + + return true; + } + + return false; + } + private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progressTracker) { @@ -4622,14 +4882,14 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polyline_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polyline_impl_b, tolerance, false); boolean b_boundaries_intersect = false; while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4708,14 +4968,14 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polyline_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polyline_impl_b, tolerance, false); boolean b_boundaries_intersect = false; while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4796,82 +5056,34 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progress_tracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polyline_impl_b, tolerance); - - boolean b_boundaries_intersect = false; - - while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 2) { - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return false; - } - - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) - } - } - - if (!b_boundaries_intersect) { - segIterB.resetToVertex(0); - Segment segmentB = segIterB.nextSegment(); - - Point2D ptB = new Point2D(); - segmentB.getCoord2D(0.5, ptB); - return (PolygonUtils.isPointInPolygon2D(polygon_a, ptB, 0.0) == PolygonUtils.PiPResult.PiPInside); - } - - // We can clip polygon_a to the extent of polyline_b - Envelope2D envBInflated = new Envelope2D(); - polyline_b.queryEnvelope2D(envBInflated); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - Polygon _polygonA; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, - tolerance, 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( - _polygonA, polyline_b, tolerance, scl, progress_tracker); - - return bRelation; + boolean[] b_result_known = new boolean[1]; + b_result_known[0] = false; + boolean res = polygonContainsMultiPath_(polygon_a, polyline_b, tolerance, b_result_known, progress_tracker); + + if (b_result_known[0]) + return res; + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polyline_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA = null; + + if (polygon_a.getPointCount() > 10) + { + _polygonA = (Polygon)Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); + if (_polygonA.isEmpty()) + return false; + } + else + { + _polygonA = polygon_a; + } + + boolean bContains = RelationalOperationsMatrix.polygonContainsPolyline_(_polygonA, polyline_b, tolerance, progress_tracker); + return bContains; } private static boolean polygonContainsPointImpl_(Polygon polygon_a, @@ -4987,171 +5199,6 @@ int compareOverlapEvents_(int o_1, int o_2) { return 1; } - static final class Pair_wise_intersector { - - Pair_wise_intersector(MultiPathImpl multi_path_impl_a, - MultiPathImpl multi_path_impl_b, double tolerance) { - m_b_quad_tree = false; - - GeometryAccelerators geometry_accelerators_a = multi_path_impl_a - ._getAccelerators(); - - if (geometry_accelerators_a != null) { - QuadTreeImpl qtree_a = geometry_accelerators_a.getQuadTree(); - - if (qtree_a != null) { - m_b_done = false; - m_tolerance = tolerance; - m_quad_tree = qtree_a; - m_qt_iter = m_quad_tree.getIterator(); - m_b_quad_tree = true; - m_b_swap_elements = true; - m_seg_iter = multi_path_impl_b.querySegmentIterator(); - m_function = State.next_path; - } - } - - if (!m_b_quad_tree) { - GeometryAccelerators geometry_accelerators_b = multi_path_impl_b - ._getAccelerators(); - - if (geometry_accelerators_b != null) { - QuadTreeImpl qtree_b = geometry_accelerators_b - .getQuadTree(); - - if (qtree_b != null) { - m_b_done = false; - m_tolerance = tolerance; - m_quad_tree = qtree_b; - m_qt_iter = m_quad_tree.getIterator(); - m_b_quad_tree = true; - m_b_swap_elements = false; - m_seg_iter = multi_path_impl_a.querySegmentIterator(); - m_function = State.next_path; - } - } - } - - if (!m_b_quad_tree) { - m_intersector = InternalUtils.getEnvelope2DIntersector( - multi_path_impl_a, multi_path_impl_b, tolerance); - } - } - - boolean next() { - if (m_b_quad_tree) { - if (m_b_done) { - return false; - } - - boolean b_searching = true; - while (b_searching) { - switch (m_function) { - case State.next_path: - b_searching = next_path_(); - break; - case State.next_segment: - b_searching = next_segment_(); - break; - case State.iterate: - b_searching = iterate_(); - break; - } - - } - - if (m_b_done) { - return false; - } - - return true; - } - - if (m_intersector == null) { - return false; - } - - return m_intersector.next(); - } - - int get_red_element() { - if (m_b_quad_tree) { - if (!m_b_swap_elements) { - return m_seg_iter.getStartPointIndex(); - } - - return m_quad_tree.getElement(m_element_handle); - } - - return m_intersector.getRedElement(m_intersector.getHandleA()); - } - - int get_blue_element() { - if (m_b_quad_tree) { - if (m_b_swap_elements) { - return m_seg_iter.getStartPointIndex(); - } - - return m_quad_tree.getElement(m_element_handle); - } - - return m_intersector.getBlueElement(m_intersector.getHandleB()); - } - - private boolean next_path_() { - if (!m_seg_iter.nextPath()) { - m_b_done = true; - return false; - } - - m_function = State.next_segment; - return true; - } - - private boolean next_segment_() { - if (!m_seg_iter.hasNextSegment()) { - m_function = State.next_path; - return true; - } - - Segment segment = m_seg_iter.nextSegment(); - m_qt_iter.resetIterator(segment, m_tolerance); - m_function = State.iterate; - return true; - } - - boolean iterate_() { - m_element_handle = m_qt_iter.next(); - if (m_element_handle == -1) { - m_function = State.next_segment; - return true; - } - - return false; - } - - private interface State { - - static final int next_path = 0; - static final int next_segment = 1; - static final int iterate = 2; - } - - // Quad_tree - private boolean m_b_quad_tree; - private boolean m_b_done; - private boolean m_b_swap_elements; - private double m_tolerance; - private int m_element_handle; - private QuadTreeImpl m_quad_tree; - private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; - private SegmentIteratorImpl m_seg_iter; - private int m_function; - - // Envelope_2D_intersector - private Envelope2DIntersectorImpl m_intersector; - } - static final class Accelerate_helper { static boolean accelerate_geometry(Geometry geometry, SpatialReference sr, @@ -5173,18 +5220,18 @@ static boolean accelerate_geometry(Geometry geometry, bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) ._buildQuadTreeAccelerator(accel_degree); - if (type == Geometry.Type.Polygon - && GeometryAccelerators.canUsePathEnvelopes(geometry) + if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) + && GeometryAccelerators.canUseQuadTreeForPaths(geometry) && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) bAccelerated |= ((MultiPathImpl) geometry._getImpl()) - ._buildPathEnvelopesAccelerator(accel_degree); + ._buildQuadTreeForPathsAccelerator(accel_degree); return bAccelerated; } - static boolean can_accelerate_geometry(Geometry geometry) { - return GeometryAccelerators.canUseRasterizedGeometry(geometry) - || GeometryAccelerators.canUseQuadTree(geometry); - } - } + static boolean can_accelerate_geometry(Geometry geometry) { + return GeometryAccelerators.canUseRasterizedGeometry(geometry) + || GeometryAccelerators.canUseQuadTree(geometry) || GeometryAccelerators.canUseQuadTreeForPaths(geometry); + } + } } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index a3b3f02d..1ad87dc4 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -26,9 +26,11 @@ class RelationalOperationsMatrix { private TopoGraph m_topo_graph; private int[] m_matrix; + private int[] m_max_dim; private boolean[] m_perform_predicates; private String m_scl; - private int m_predicates; + private int m_predicates_half_edge; + private int m_predicates_cluster; private int m_predicate_count; private int m_cluster_index_a; private int m_cluster_index_b; @@ -60,7 +62,19 @@ private interface Predicates { // string. static boolean relate(Geometry geometry_a, Geometry geometry_b, SpatialReference sr, String scl, ProgressTracker progress_tracker) { - int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), + + if (scl.length() != 9) + throw new GeometryException("relation string length has to be 9 characters"); + + for (int i = 0; i < 9; i++) + { + char c = scl.charAt(i); + + if (c != '*' && c != 'T' && c != 'F' && c != '0' && c != '1' && c != '2') + throw new GeometryException("relation string"); + } + + int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), geometry_b.getDimension()); if (relation != RelationalOperations.Relation.unknown) @@ -81,6 +95,9 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, Geometry _geometryA = convertGeometry_(geometry_a, tolerance); Geometry _geometryB = convertGeometry_(geometry_b, tolerance); + if (_geometryA.isEmpty() || _geometryB.isEmpty()) + return relateEmptyGeometries_(_geometryA, _geometryB, scl); + int typeA = _geometryA.getType().value(); int typeB = _geometryB.getType().value(); @@ -218,7 +235,10 @@ private RelationalOperationsMatrix() { m_predicate_count = 0; m_topo_graph = new TopoGraph(); m_matrix = new int[9]; + m_max_dim = new int[9]; m_perform_predicates = new boolean[9]; + m_predicates_half_edge = -1; + m_predicates_cluster = -1; } // Returns true if the relation holds. @@ -238,7 +258,7 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, env_a, env_b, tolerance, progress_tracker); if (b_disjoint) { - relOps.areaAreaDisjointPredicates_(); + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); bRelationKnown = true; } @@ -250,13 +270,13 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, tolerance, false); if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaAreaDisjointPredicates_(); + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); bRelationKnown = true; } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaAreaContainsPredicates_(); + relOps.areaAreaContainsPredicates_(polygon_b); bRelationKnown = true; } else if (relation == RelationalOperations.Relation.within) { - relOps.areaAreaWithinPredicates_(); + relOps.areaAreaWithinPredicates_(polygon_a); bRelationKnown = true; } } @@ -275,6 +295,123 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, return bRelation; } + // The relation is based on the simplified-Polygon A containing Polygon B, which may be non-simple. + static boolean polygonContainsPolygon_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progress_tracker) + { + assert(!polygon_a.isEmpty()); + assert(!polygon_b.isEmpty()); + + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_("T*****F**"); + relOps.setAreaAreaPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } + + if (!bRelationKnown) + { + // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. + int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) + { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } + else if (relation == RelationalOperations.Relation.contains) + { + relOps.areaAreaContainsPredicates_(polygon_b); + bRelationKnown = true; + } + else if (relation == RelationalOperations.Relation.within) + { + relOps.areaAreaWithinPredicates_(polygon_a); + bRelationKnown = true; + } + } + + if (bRelationKnown) + { + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + + CrackAndCluster.execute(edit_shape, tolerance, progress_tracker, false); + Polyline boundary_b = (Polyline)edit_shape.getGeometry(geom_b).getBoundary(); + edit_shape.filterClosePoints(0, true, true); + Simplificator.execute(edit_shape, geom_a, -1, false, progress_tracker); + + // Make sure Polygon A has exterior + // If the simplified Polygon A does not have interior, then it cannot contain anything. + if (edit_shape.getPointCount(geom_a) == 0) + return false; + + Simplificator.execute(edit_shape, geom_b, -1, false, progress_tracker); + + relOps.setEditShape_(edit_shape, progress_tracker); + + // We see if the simplified Polygon A contains the simplified Polygon B. + + boolean b_empty = edit_shape.getPointCount(geom_b) == 0; + + if (!b_empty) + {//geom_b has interior + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + + if (!bContains) + return bContains; + } + + Polygon polygon_simple_a = (Polygon)edit_shape.getGeometry(geom_a); + edit_shape = new EditShape(); + geom_a = edit_shape.addGeometry(polygon_simple_a); + geom_b = edit_shape.addGeometry(boundary_b); + + int pc1 = polygon_simple_a.getPointCount(); + int pc2 = boundary_b.getPointCount(); + + for (int i = 0; i < pc2; i++) + { + Point2D p = boundary_b.getXY(i); + i = i; + } + + boolean isclosed0 = boundary_b.isClosedPath(0); + boolean isclosed1 = boundary_b.isClosedPath(1); + + String s1 = OperatorExportToJson.local().execute(null, polygon_simple_a); + String s2 = OperatorExportToJson.local().execute(null, boundary_b); + relOps.setEditShape_(edit_shape, progress_tracker); + + // Check no interior lines of the boundary intersect the exterior + relOps.m_predicate_count = 0; + relOps.resetMatrix_(); + relOps.setPredicates_(b_empty ? "T*****F**" : "******F**"); + relOps.setAreaLinePredicates_(); + + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + // Returns true if the relation holds. static boolean polygonRelatePolyline_(Polygon polygon_a, Polyline polyline_b, double tolerance, String scl, @@ -293,7 +430,7 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, env_a, env_b, tolerance, progress_tracker); if (b_disjoint) { - relOps.areaLineDisjointPredicates_(polyline_b); // passing polyline + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline // to get boundary // information bRelationKnown = true; @@ -307,13 +444,13 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, tolerance, false); if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaLineDisjointPredicates_(polyline_b); // passing + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing // polyline to // get boundary // information bRelationKnown = true; } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaLineContainsPredicates_(polyline_b); // passing + relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing // polyline to // get boundary // information @@ -341,6 +478,66 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, return bRelation; } + static boolean polygonContainsPolyline_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progress_tracker) + { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_("T*****F**"); + relOps.setAreaLinePredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + + if (!bRelationKnown) + { + // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. + int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) + { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + else if (relation == RelationalOperations.Relation.contains) + { + relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + } + + if (bRelationKnown) + { + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, progress_tracker); + + // Make sure Polygon A has exterior + // If the simplified Polygon A does not have interior, then it cannot contain anything. + if (edit_shape.getPointCount(geom_a) == 0) + return false; + + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + // Returns true if the relation holds static boolean polygonRelateMultiPoint_(Polygon polygon_a, MultiPoint multipoint_b, double tolerance, String scl, @@ -359,7 +556,7 @@ static boolean polygonRelateMultiPoint_(Polygon polygon_a, env_a, env_b, tolerance, progress_tracker); if (b_disjoint) { - relOps.areaPointDisjointPredicates_(); + relOps.areaPointDisjointPredicates_(polygon_a); bRelationKnown = true; } @@ -371,10 +568,10 @@ static boolean polygonRelateMultiPoint_(Polygon polygon_a, tolerance, false); if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaPointDisjointPredicates_(); + relOps.areaPointDisjointPredicates_(polygon_a); bRelationKnown = true; } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaPointContainsPredicates_(); + relOps.areaPointContainsPredicates_(polygon_a); bRelationKnown = true; } } @@ -548,144 +745,189 @@ static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, // Returns true if the relation holds. static boolean polygonRelatePoint_(Polygon polygon_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setAreaPointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, - env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaPointDisjointPredicates_(); - bRelationKnown = true; - } - - if (!bRelationKnown) { - PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( - polygon_a, pt_b, tolerance); // uses accelerator - - if (res == PolygonUtils.PiPResult.PiPInside) { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else if (res == PolygonUtils.PiPResult.PiPBoundary) { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, scl); - return bRelation; + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.areaPointDisjointPredicates_(polygon_a); + bRelationKnown = true; + } + + if (!bRelationKnown) + { + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); // uses accelerator + + if (res == PolygonUtils.PiPResult.PiPInside) + {// polygon must have area + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + else if (res == PolygonUtils.PiPResult.PiPBoundary) + { + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + + double area = polygon_a.calculateArea2D(); + + if (area != 0) + { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; + } + else + { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + + Envelope2D env = new Envelope2D(); + polygon_a.queryEnvelope2D(env); + relOps.m_matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? -1 : 1); + } + } + else + { + relOps.areaPointDisjointPredicates_(polygon_a); + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, scl); + return bRelation; } // Returns true if the relation holds. static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setLinePointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, - env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.linePointDisjointPredicates_(polyline_a); - bRelationKnown = true; - } - - if (!bRelationKnown) { - MultiPoint boundary_a = null; - boolean b_boundary_contains_point_known = false; - boolean b_boundary_contains_point = false; - - if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] - || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - boolean b_intersects = RelationalOperations - .linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); - - if (b_intersects) { - if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) { - boundary_a = (MultiPoint) Boundary.calculate( - polyline_a, progress_tracker); - b_boundary_contains_point = !RelationalOperations - .multiPointDisjointPointImpl_(boundary_a, pt_b, - tolerance, progress_tracker); - b_boundary_contains_point_known = true; - - if (b_boundary_contains_point) - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - else - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - if (boundary_a != null && boundary_a.isEmpty()) { - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - } else { - if (!b_boundary_contains_point_known) { - if (boundary_a == null) - boundary_a = (MultiPoint) Boundary.calculate( - polyline_a, progress_tracker); - - b_boundary_contains_point = !RelationalOperations - .multiPointDisjointPointImpl_(boundary_a, pt_b, - tolerance, progress_tracker); - b_boundary_contains_point_known = true; - } - - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 - : -1); - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - if (boundary_a != null && boundary_a.isEmpty()) { - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; - } else { - if (b_boundary_contains_point_known - && !b_boundary_contains_point) { - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; - } else { - if (boundary_a == null) - boundary_a = (MultiPoint) Boundary.calculate( - polyline_a, progress_tracker); - - boolean b_boundary_equals_point = RelationalOperations - .multiPointEqualsPoint_(boundary_a, point_b, - tolerance, progress_tracker); - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 - : 0); - } - } - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLinePointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + + if (!bRelationKnown) + { + MultiPoint boundary_a = null; + boolean b_boundary_contains_point_known = false; + boolean b_boundary_contains_point = false; + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + boolean b_intersects = RelationalOperations.linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); + + if (b_intersects) + { + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + boundary_a = (MultiPoint)Boundary.calculate(polyline_a, progress_tracker); + b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); + b_boundary_contains_point_known = true; + + if (b_boundary_contains_point) + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + else + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + else + { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + if (boundary_a != null && boundary_a.isEmpty()) + { + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + } + else + { + if (!b_boundary_contains_point_known) + { + if (boundary_a == null) + boundary_a = (MultiPoint)Boundary.calculate(polyline_a, progress_tracker); + + b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); + b_boundary_contains_point_known = true; + } + + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 : -1); + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + if (boundary_a != null && boundary_a.isEmpty()) + { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + } + else + { + if (b_boundary_contains_point_known && !b_boundary_contains_point) + { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; + } + else + { + if (boundary_a == null) + boundary_a = (MultiPoint)Boundary.calculate(polyline_a, progress_tracker); + + boolean b_boundary_equals_point = RelationalOperations.multiPointEqualsPoint_(boundary_a, point_b, tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 : 0); + } + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + boolean b_has_length = polyline_a.calculateLength2D() != 0; + + if (b_has_length) + { + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 1; + } + else + { + // all points are interior + MultiPoint interior_a = new MultiPoint(polyline_a.getDescription()); + interior_a.add(polyline_a, 0, polyline_a.getPointCount()); + boolean b_interior_equals_point = RelationalOperations.multiPointEqualsPoint_(interior_a, point_b, tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.InteriorExterior] = (b_interior_equals_point ? -1 : 0); + } + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; } // Returns true if the relation holds. @@ -812,6 +1054,85 @@ private static boolean relationCompare_(int[] matrix, String scl) { return true; } + static boolean relateEmptyGeometries_(Geometry geometry_a, Geometry geometry_b, String scl) + { + int[] matrix = new int[9]; + + if (geometry_a.isEmpty() && geometry_b.isEmpty()) + { + for (int i = 0; i < 9; i++) + matrix[i] = -1; + + return relationCompare_(matrix, scl); + } + + boolean b_transpose = false; + + Geometry g_a; + Geometry g_b; + + if (!geometry_a.isEmpty()) + { + g_a = geometry_a; + g_b = geometry_b; + } + else + { + g_a = geometry_b; + g_b = geometry_a; + b_transpose = true; + } + + matrix[MatrixPredicate.InteriorInterior] = -1; + matrix[MatrixPredicate.InteriorBoundary] = -1; + matrix[MatrixPredicate.BoundaryInterior] = -1; + matrix[MatrixPredicate.BoundaryBoundary] = -1; + matrix[MatrixPredicate.ExteriorInterior] = -1; + matrix[MatrixPredicate.ExteriorBoundary] = -1; + + matrix[MatrixPredicate.ExteriorExterior] = 2; + + int type = g_a.getType().value(); + + if (Geometry.isMultiPath(type)) + { + if (type == Geometry.GeometryType.Polygon) + { + double area = ((Polygon)g_a).calculateArea2D(); + + if (area != 0) + { + matrix[MatrixPredicate.InteriorExterior] = 2; + matrix[MatrixPredicate.BoundaryExterior] = 1; + } + else + { + matrix[MatrixPredicate.BoundaryExterior] = -1; + + Envelope2D env = new Envelope2D(); + g_a.queryEnvelope2D(env); + matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); + } + } + else + { + boolean b_has_length = ((Polyline)g_a).calculateLength2D() != 0; + matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + matrix[MatrixPredicate.BoundaryExterior] = (Boundary.hasNonEmptyBoundary((Polyline)g_a, null) ? 0 : -1); + } + } + else + { + matrix[MatrixPredicate.InteriorExterior] = 0; + matrix[MatrixPredicate.BoundaryExterior] = -1; + } + + if (b_transpose) + transposeMatrix_(matrix); + + return relationCompare_(matrix, scl); + } + // Checks whether scl string is a predefined relation. private static int getPredefinedRelation_(String scl, int dim_a, int dim_b) { if (equals_(scl)) @@ -971,36 +1292,41 @@ private static boolean overlaps_(String scl, int dim_a, int dim_b) { // the geometry and/or a boundary index of the geometry. private static void markClusterEndPoints_(int geometry, TopoGraph topoGraph, int clusterIndex) { - EditShape edit_shape = topoGraph.getShape(); + int id = topoGraph.getGeometryID(geometry); - for (int path = edit_shape.getFirstPath(geometry); path != -1; path = edit_shape - .getNextPath(path)) { - int vertexFirst = edit_shape.getFirstVertex(path); - int vertexLast = edit_shape.getLastVertex(path); - boolean b_closed = (vertexFirst == vertexLast); + for (int cluster = topoGraph.getFirstCluster(); cluster != -1; cluster = topoGraph.getNextCluster(cluster)) + { + int cluster_parentage = topoGraph.getClusterParentage(cluster); - int vertex = vertexFirst; + if ((cluster_parentage & id) == 0) + continue; - do { - int cluster = topoGraph.getClusterFromVertex(vertex); - int index = topoGraph - .getClusterUserIndex(cluster, clusterIndex); - - if (!b_closed - && (vertex == vertexFirst || vertex == vertexLast)) { - if (index == -1) - topoGraph.setClusterUserIndex(cluster, clusterIndex, 1); - else - topoGraph.setClusterUserIndex(cluster, clusterIndex, - index + 1); - } else { - if (index == -1) - topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); - } + int first_half_edge = topoGraph.getClusterHalfEdge(cluster); + if (first_half_edge == -1) + { + topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); + continue; + } - vertex = edit_shape.getNextVertex(vertex); - } while (vertex != vertexFirst && vertex != -1); - } + int next_half_edge = first_half_edge; + int index = 0; + + do + { + int half_edge = next_half_edge; + int half_edge_parentage = topoGraph.getHalfEdgeParentage(half_edge); + + if ((half_edge_parentage & id) != 0) + index++; + + next_half_edge = topoGraph.getHalfEdgeNext(topoGraph.getHalfEdgeTwin(half_edge)); + + } while (next_half_edge != first_half_edge); + + topoGraph.setClusterUserIndex(cluster, clusterIndex, index); + } + + return; } private static String getTransposeMatrix_(String scl) { @@ -1025,21 +1351,24 @@ private static String getTransposeMatrix_(String scl) { // 2: 2-dimension intersection private void resetMatrix_() { for (int i = 0; i < 9; i++) - m_matrix[i] = -2; + { + m_matrix[i] = -2; + m_max_dim[i] = -2; + } } - private void transposeMatrix_() { - int temp1 = m_matrix[1]; - int temp2 = m_matrix[2]; - int temp5 = m_matrix[5]; + private static void transposeMatrix_(int[] matrix) { + int temp1 = matrix[1]; + int temp2 = matrix[2]; + int temp5 = matrix[5]; - m_matrix[1] = m_matrix[3]; - m_matrix[2] = m_matrix[6]; - m_matrix[5] = m_matrix[7]; + matrix[1] = matrix[3]; + matrix[2] = matrix[6]; + matrix[5] = matrix[7]; - m_matrix[3] = temp1; - m_matrix[6] = temp2; - m_matrix[7] = temp5; + matrix[3] = temp1; + matrix[6] = temp2; + matrix[7] = temp5; } // Sets the relation predicates from the scl string. @@ -1066,193 +1395,254 @@ private void setRemainingPredicatesToFalse_() { } // Checks whether the predicate is known. - private boolean isPredicateKnown_(int predicate, int dim) { - assert (m_scl.charAt(predicate) != '*'); - - if (m_matrix[predicate] == -2) - return false; - - if (m_matrix[predicate] == -1) { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - - if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') { - if (m_matrix[predicate] < dim) { - return false; - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } + private boolean isPredicateKnown_(int predicate) { + assert(m_scl.charAt(predicate) != '*'); + + if (m_matrix[predicate] == -2) + return false; + + if (m_matrix[predicate] == -1) + { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + + if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') + { + if (m_matrix[predicate] < m_max_dim[predicate]) + { + return false; + } + else + { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + } + else + { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } } // Sets the area-area predicates function. private void setAreaAreaPredicates_() { - m_predicates = Predicates.AreaAreaPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_half_edge = Predicates.AreaAreaPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 2; + m_max_dim[MatrixPredicate.InteriorBoundary] = 1; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 2; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the area-line predicate function. private void setAreaLinePredicates_() { - m_predicates = Predicates.AreaLinePredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - m_matrix[MatrixPredicate.InteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.InteriorExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_half_edge = Predicates.AreaLinePredicates; + m_predicates_cluster = Predicates.AreaPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 1; + m_max_dim[MatrixPredicate.InteriorBoundary] = 0; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 1; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the line-line predicates function. private void setLineLinePredicates_() { - m_predicates = Predicates.LineLinePredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_half_edge = Predicates.LineLinePredicates; + m_predicates_cluster = Predicates.LinePointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 1; + m_max_dim[MatrixPredicate.InteriorBoundary] = 0; + m_max_dim[MatrixPredicate.InteriorExterior] = 1; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; + m_max_dim[MatrixPredicate.BoundaryExterior] = 0; + m_max_dim[MatrixPredicate.ExteriorInterior] = 1; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the area-point predicate function. private void setAreaPointPredicates_() { - m_predicates = Predicates.AreaPointPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - m_matrix[MatrixPredicate.InteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.InteriorExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - m_matrix[MatrixPredicate.BoundaryExterior] = 1; // Always true - m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_cluster = Predicates.AreaPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the line-point predicates function. private void setLinePointPredicates_() { - m_predicates = Predicates.LinePointPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - m_matrix[MatrixPredicate.InteriorExterior] = 1; // Always true - m_perform_predicates[MatrixPredicate.InteriorExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_cluster = Predicates.LinePointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 1; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 0; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the point-point predicates function. private void setPointPointPredicates_() { - m_predicates = Predicates.PointPointPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_cluster = Predicates.PointPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 0; + m_max_dim[MatrixPredicate.BoundaryInterior] = -1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = -1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Invokes the 9 relational predicates of area vs area. @@ -1262,175 +1652,228 @@ private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { interiorAreaInteriorArea_(half_edge, id_a, id_b); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 2); + MatrixPredicate.InteriorInterior); } if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { interiorAreaBoundaryArea_(half_edge, id_a, MatrixPredicate.InteriorBoundary); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary, 1); + MatrixPredicate.InteriorBoundary); } if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { interiorAreaExteriorArea_(half_edge, id_a, id_b, MatrixPredicate.InteriorExterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior, 2); + MatrixPredicate.InteriorExterior); } if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { interiorAreaBoundaryArea_(half_edge, id_b, MatrixPredicate.BoundaryInterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 1); + MatrixPredicate.BoundaryInterior); } if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { boundaryAreaBoundaryArea_(half_edge, id_a, id_b); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary, 1); + MatrixPredicate.BoundaryBoundary); } if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { boundaryAreaExteriorArea_(half_edge, id_a, id_b, MatrixPredicate.BoundaryExterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 1); + MatrixPredicate.BoundaryExterior); } if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { interiorAreaExteriorArea_(half_edge, id_b, id_a, MatrixPredicate.ExteriorInterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 2); + MatrixPredicate.ExteriorInterior); } if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { boundaryAreaExteriorArea_(half_edge, id_b, id_a, MatrixPredicate.ExteriorBoundary); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary, 1); + MatrixPredicate.ExteriorBoundary); } return bRelationKnown; } - private void areaAreaDisjointPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.InteriorExterior] = 2; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = 2; - m_matrix[MatrixPredicate.ExteriorBoundary] = 1; - - // all other predicates should already be set by - // set_area_area_predicates - } - - private void areaAreaContainsPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = 2; - m_matrix[MatrixPredicate.InteriorBoundary] = 1; - m_matrix[MatrixPredicate.InteriorExterior] = 2; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; - - // all other predicates should already be set by - // set_area_area_predicates - } - - private void areaAreaWithinPredicates_() { - areaAreaContainsPredicates_(); - transposeMatrix_(); - } - - private void areaLineDisjointPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = 1; - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( - polyline, null); - m_matrix[MatrixPredicate.ExteriorBoundary] = (has_non_empty_boundary ? 0 - : -1); - } - } - - private void areaLineContainsPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = 1; - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( - polyline, null); - m_matrix[MatrixPredicate.InteriorBoundary] = (has_non_empty_boundary ? 0 - : -1); - } - - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; - } - - private void areaPointDisjointPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - - private void areaPointContainsPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = 0; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } + private void areaAreaDisjointPredicates_(Polygon polygon_a, Polygon polygon_b) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + areaGeomContainsOrDisjointPredicates_(polygon_a, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.ExteriorInterior] ? MatrixPredicate.ExteriorInterior : -1, m_scl.charAt(MatrixPredicate.ExteriorInterior), m_perform_predicates[MatrixPredicate.ExteriorBoundary] ? MatrixPredicate.ExteriorBoundary : -1, m_scl.charAt(MatrixPredicate.ExteriorBoundary)); + } + + private void areaGeomContainsOrDisjointPredicates_(Polygon polygon, int matrix_interior, char c1, int matrix_boundary, char c2) + { + if (matrix_interior != -1 || matrix_boundary != -1) + { + boolean has_area = ((c1 != 'T' && c1 != 'F' && matrix_interior != -1) || (c2 != 'T' && c2 != 'F' && matrix_boundary != -1) ? polygon.calculateArea2D() != 0 : true); + + if (has_area) + { + if (matrix_interior != -1) + m_matrix[matrix_interior] = 2; + if (matrix_boundary != -1) + m_matrix[matrix_boundary] = 1; + } + else + { + if (matrix_boundary != -1) + m_matrix[matrix_boundary] = -1; + + if (matrix_interior != -1) + { + Envelope2D env = new Envelope2D(); + polygon.queryEnvelope2D(env); + m_matrix[matrix_interior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); + } + } + } + } + + private void areaAreaContainsPredicates_(Polygon polygon_b) { + m_matrix[MatrixPredicate.InteriorExterior] = 2; // im assuming its a proper contains. + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + + areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.InteriorInterior] ? MatrixPredicate.InteriorInterior : -1, m_scl.charAt(MatrixPredicate.InteriorInterior), m_perform_predicates[MatrixPredicate.InteriorBoundary] ? MatrixPredicate.InteriorBoundary : -1, m_scl.charAt(MatrixPredicate.InteriorBoundary)); + + // all other predicates should already be set by set_area_area_predicates + } + + private void areaAreaWithinPredicates_(Polygon polygon_a) { + areaAreaContainsPredicates_(polygon_a); + transposeMatrix_(m_matrix); + } + + private void areaLineDisjointPredicates_(Polygon polygon, Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary ? 0 : -1; + } + + areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + } + + private void areaLineContainsPredicates_(Polygon polygon, Polyline polyline) { + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + char c = m_scl.charAt(MatrixPredicate.InteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.InteriorBoundary] = has_non_empty_boundary ? 0 : -1; + } + + m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + } + + private void areaPointDisjointPredicates_(Polygon polygon) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + + areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + } + + private void areaPointContainsPredicates_(Polygon polygon) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } private void lineLineDisjointPredicates_(Polyline polyline_a, Polyline polyline_b) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.InteriorExterior] = 1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary( - polyline_a, null); - m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary_a ? 0 - : -1); - } - - m_matrix[MatrixPredicate.ExteriorInterior] = 1; - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary( - polyline_b, null); - m_matrix[MatrixPredicate.ExteriorBoundary] = (has_non_empty_boundary_b ? 0 - : -1); - } + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + char c = m_scl.charAt(MatrixPredicate.InteriorExterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline_a.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary(polyline_a, null); + m_matrix[MatrixPredicate.BoundaryExterior] = has_non_empty_boundary_a ? 0 : -1; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline_b.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary(polyline_b, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary_b ? 0 : -1; + } } private void linePointDisjointPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( - polyline, null); - m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 - : -1); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + char c = m_scl.charAt(MatrixPredicate.InteriorExterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + } - m_matrix[MatrixPredicate.ExteriorInterior] = 0; + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 : -1); + } + + m_matrix[MatrixPredicate.ExteriorInterior] = 0; } private void pointPointDisjointPredicates_() { @@ -1441,197 +1884,211 @@ private void pointPointDisjointPredicates_() { // Invokes the 9 relational predicates of area vs Line. private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryAreaExteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorAreaInteriorLine_(half_edge, id_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary, 0); - } - - return bRelationKnown; + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorAreaInteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + exteriorAreaInteriorLine_(half_edge, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of Line vs Line. private boolean lineLinePredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, - m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, - m_cluster_index_b, MatrixPredicate.InteriorBoundary); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorLineExteriorLine_(half_edge, id_a, id_b, - MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, - m_cluster_index_a, MatrixPredicate.BoundaryInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, - m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, - MatrixPredicate.BoundaryExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 0); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorLineExteriorLine_(half_edge, id_b, id_a, - MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, - MatrixPredicate.ExteriorBoundary); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary, 0); - } - - return bRelationKnown; + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b, MatrixPredicate.InteriorBoundary); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorLineExteriorLine_(half_edge, id_a, id_b, MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, m_cluster_index_a, MatrixPredicate.BoundaryInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, MatrixPredicate.BoundaryExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + interiorLineExteriorLine_(half_edge, id_b, id_a, MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, MatrixPredicate.ExteriorBoundary); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of area vs Point. private boolean areaPointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; + boolean bRelationKnown = true; - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryAreaInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorAreaExteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorAreaInteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + boundaryAreaInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } - return bRelationKnown; + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryAreaExteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + exteriorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of Line vs Point. private boolean linePointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; + boolean bRelationKnown = true; - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 0); - } + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorLineInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } - return bRelationKnown; + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + exteriorLineInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of Point vs Point. private boolean pointPointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; + boolean bRelationKnown = true; - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorPointInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorPointInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorPointExteriorPoint_(cluster, id_a, id_b, - MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorPointExteriorPoint_(cluster, id_a, id_b, MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorPointExteriorPoint_(cluster, id_b, id_a, - MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + interiorPointExteriorPoint_(cluster, id_b, id_a, MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } - return bRelationKnown; + return bRelationKnown; } // Relational predicate to determine if the interior of area A intersects @@ -1766,6 +2223,19 @@ private void interiorAreaBoundaryLine_(int half_edge, int id_a, int id_b, } } + private void interiorAreaExteriorLine_(int half_edge, int id_a, int id_b) + { + if (m_matrix[MatrixPredicate.InteriorExterior] == 2) + return; + + int half_edge_parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((half_edge_parentage & id_a) != 0) + {//half edge of polygon + m_matrix[MatrixPredicate.InteriorExterior] = 2; + } + } + // Relational predicate to determine if the boundary of area A intersects // with the interior of Line B. private void boundaryAreaInteriorLine_(int half_edge, int id_a, int id_b, @@ -2047,6 +2517,19 @@ private void interiorAreaInteriorPoint_(int cluster, int id_a) { } } + private void interiorAreaExteriorPoint_(int cluster, int id_a) + { + if (m_matrix[MatrixPredicate.InteriorExterior] == 2) + return; + + int cluster_parentage = m_topo_graph.getClusterParentage(cluster); + + if ((cluster_parentage & id_a) != 0) + { + m_matrix[MatrixPredicate.InteriorExterior] = 2; + } + } + // Relational predicate to determine if the boundary of area A intersects // with the interior of Point B. private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { @@ -2060,6 +2543,19 @@ private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { } } + private void boundaryAreaExteriorPoint_(int cluster, int id_a) + { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) + return; + + int cluster_parentage = m_topo_graph.getClusterParentage(cluster); + + if ((cluster_parentage & id_a) != 0) + { + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + } + } + // Relational predicate to determine if the exterior of area A intersects // with the interior of Point B. private void exteriorAreaInteriorPoint_(int cluster, int id_a) { @@ -2097,6 +2593,34 @@ private void interiorLineInteriorPoint_(int cluster, int id_a, int id_b, } } + private void interiorLineExteriorPoint_(int cluster, int id_a, int id_b, int cluster_index_a) + { + if (m_matrix[MatrixPredicate.InteriorExterior] == 1) + return; + + int half_edge_a = m_topo_graph.getClusterHalfEdge(cluster); + + if (half_edge_a != -1) + { + m_matrix[MatrixPredicate.InteriorExterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.InteriorExterior] != 0) + { + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_b) == 0) + { + assert(m_topo_graph.getClusterUserIndex(cluster, cluster_index_a) % 2 == 0); + m_matrix[MatrixPredicate.InteriorExterior] = 0; + return; + } + } + + return; + } + // Relational predicate to determine if the boundary of Line A intersects // with the interior of Point B. private void boundaryLineInteriorPoint_(int cluster, int id_a, int id_b, @@ -2189,6 +2713,27 @@ private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph .getNextCluster(cluster)) { int first_half_edge = m_topo_graph.getClusterHalfEdge(cluster); + if (first_half_edge == -1) + { + if (m_predicates_cluster != -1) + { + // Treat cluster as an interior point + switch (m_predicates_cluster) + { + case Predicates.AreaPointPredicates: + bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); + break; + case Predicates.LinePointPredicates: + bRelationKnown = linePointPredicates_(cluster, id_a, id_b); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + continue; + } + int next_half_edge = first_half_edge; do { @@ -2199,7 +2744,7 @@ private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { if (visited != 1) { do { // Invoke relational predicates - switch (m_predicates) { + switch (m_predicates_half_edge) { case Predicates.AreaAreaPredicates: bRelationKnown = areaAreaPredicates_(half_edge, id_a, id_b); @@ -2254,7 +2799,7 @@ private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph .getNextCluster(cluster)) { // Invoke relational predicates - switch (m_predicates) { + switch (m_predicates_cluster) { case Predicates.AreaPointPredicates: bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); break; @@ -2290,12 +2835,13 @@ private void setEditShapeCrackAndCluster_(EditShape shape, private void editShapeCrackAndCluster_(EditShape shape, double tolerance, ProgressTracker progress_tracker) { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + CrackAndCluster.execute(shape, tolerance, progress_tracker, false); //do not filter degenerate segments. + shape.filterClosePoints(0, true, true);//remove degeneracies from polygon geometries. for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape .getNextGeometry(geometry)) { if (shape.getGeometryType(geometry) == Geometry.Type.Polygon .value()) - Simplificator.execute(shape, geometry, -1, false); + Simplificator.execute(shape, geometry, -1, false, progress_tracker); } } diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index ba82fd2f..ffbadb18 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -246,25 +246,21 @@ public boolean intersect(double tolerance, boolean b_intersecting) { double ptWeight; - Point2D pt; + Point2D pt = new Point2D(); if (rank1 == rank2) {// for equal ranks use weighted sum Point2D pt_1 = new Point2D(); line_1.getCoord2D(t1, pt_1); - pt_1.scale(weight1); Point2D pt_2 = new Point2D(); line_2.getCoord2D(t2, pt_2); - pt_2.scale(weight2); - pt = new Point2D(); - pt.add(pt_1, pt_2); ptWeight = weight1 + weight2; - pt.scale(1 / ptWeight); + double t = weight2 / ptWeight; + MathUtils.lerp(pt_1, pt_2, t, pt); if (Point2D.sqrDistance(pt, pt_1) + Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) bigmove = true; } else {// for non-equal ranks, the higher rank wins if (rank1 > rank2) { - pt = new Point2D(); line_1.getCoord2D(t1, pt); ptWeight = weight1; Point2D pt_2 = new Point2D(); @@ -272,7 +268,6 @@ public boolean intersect(double tolerance, boolean b_intersecting) { if (Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) bigmove = true; } else { - pt = new Point2D(); line_2.getCoord2D(t2, pt); ptWeight = weight2; Point2D pt_1 = new Point2D(); @@ -392,17 +387,14 @@ public void intersect(double tolerance, Point pt_intersector_point, double ptWeight; - Point2D pt; + Point2D pt = new Point2D(); if (rank1 == rank2) {// for equal ranks use weighted sum Point2D pt_1 = new Point2D(); line_1.getCoord2D(t1, pt_1); - pt_1.scale(weight1); Point2D pt_2 = pt_intersector_point.getXY(); - pt_2.scale(weight2); - pt = new Point2D(); - pt.add(pt_1, pt_2); ptWeight = weight1 + weight2; - pt.scale(1 / ptWeight); + double t = weight2 / ptWeight; + MathUtils.lerp(pt_1, pt_2, t, pt); } else {// for non-equal ranks, the higher rank wins if (rank1 > rank2) { pt = new Point2D(); diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 955fea71..8234b828 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -42,6 +42,7 @@ class Simplificator { private int m_knownSimpleResult; private boolean m_bWinding; private boolean m_fixSelfTangency; + private ProgressTracker m_progressTracker; private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, @@ -370,6 +371,15 @@ public int compare(int v1, int v2) { } private boolean _simplify() { + if (m_shape.getGeometryType(m_geometry) == Polygon.Type.Polygon.value() + && m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleWinding) + + { + TopologicalOperations ops = new TopologicalOperations(); + ops.planarSimplifyNoCrackingAndCluster(m_fixSelfTangency, + m_shape, m_geometry, m_progressTracker); + assert (m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleOddEven); + } boolean bChanged = false; boolean bNeedWindingRepeat = true; boolean bWinding = false; @@ -978,13 +988,14 @@ protected Simplificator() { } public static boolean execute(EditShape shape, int geometry, - int knownSimpleResult, boolean fixSelfTangency) { + int knownSimpleResult, boolean fixSelfTangency, ProgressTracker progressTracker) { Simplificator simplificator = new Simplificator(); simplificator.m_shape = shape; // simplificator.m_bWinding = bWinding; simplificator.m_geometry = geometry; simplificator.m_knownSimpleResult = knownSimpleResult; simplificator.m_fixSelfTangency = fixSelfTangency; + simplificator.m_progressTracker = progressTracker; return simplificator._simplify(); } diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index 0bf61cd7..1146860d 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -70,6 +70,17 @@ static interface EnumInputMode { boolean m_buildChains = true; + private boolean m_dirty_check_failed = false; + private double m_check_dirty_planesweep_tolerance = Double.NaN; + + void check_dirty_planesweep(double tolerance) { + m_check_dirty_planesweep_tolerance = tolerance; + } + + boolean dirty_check_failed() { + return m_dirty_check_failed; + } + NonSimpleResult m_non_simple_result = new NonSimpleResult(); int m_pointCount;// point count processed in this Topo_graph. Used to @@ -990,9 +1001,16 @@ void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { int clusterTo = m_shape.getUserIndex(next, m_clusterIndex); assert (clusterTo != -1); - assert (cluster != clusterTo);// probably will assert for - // verticasl segments! Need to - // refactor a little + if (cluster == clusterTo) { + if (m_shape.getSegment(vertex) != null) { + assert (m_shape.getSegment(vertex).calculateLength2D() == 0); + } else { + assert (m_shape.getXY(vertex).isEqual(m_shape.getXY(next))); + } + + continue; + } + int half_edge = newHalfEdgePair_(); int twinEdge = getHalfEdgeTwin(half_edge); @@ -1182,6 +1200,7 @@ void sortHalfEdgesByAngle_(int inputMode) { } while (edge != first); if (angleSorter.size() > 1) { + boolean changed_order = true; if (angleSorter.size() > 2) { angleSorter.Sort(0, angleSorter.size(), tgac); // std::sort(angleSorter.get_ptr(), @@ -1191,12 +1210,16 @@ void sortHalfEdgesByAngle_(int inputMode) { // TopoGraphAngleComparer(this)); angleSorter.add(angleSorter.get(0)); } else { - if (compareEdgeAngles_(angleSorter.get(0), + //no need to sort most two edge cases. we only need to make sure that edges going up are sorted + if (compareEdgeAnglesForPair_(angleSorter.get(0), angleSorter.get(1)) > 0) { int tmp = angleSorter.get(0); angleSorter.set(0, angleSorter.get(1)); angleSorter.set(1, tmp); } + else { + changed_order = false; + } } // 2. get rid of duplicate edges by merging them (duplicate // edges appear at this step because we converted all @@ -1325,7 +1348,10 @@ else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { continue; } - + else { + //edges do not coincide + } + updateVertexToHalfEdgeConnection_(prevMerged, false); prevMerged = -1; ePrev = e; @@ -1333,8 +1359,27 @@ else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { ePrevTwin = eTwin; } + updateVertexToHalfEdgeConnection_(prevMerged, false); prevMerged = -1; + + if (!changed_order) { + //small optimization to avoid reconnecting if nothing changed + e0 = -1; + for (int i = 0, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + if (e == -1) + continue; + e0 = e; + break; + } + + if (first != e0) + setClusterHalfEdge_(cluster, e0); + + continue; //next cluster + } + // 3. Reconnect edges in the sorted order. The edges are // sorted counter clockwise. @@ -1582,6 +1627,7 @@ boolean removeSpikes_() { void setEditShapeImpl_(EditShape shape, int inputMode, AttributeStreamOfInt32 editShapeGeometries, ProgressTracker progress_tracker, boolean bBuildChains) { + assert(!m_dirty_check_failed); assert (editShapeGeometries == null || editShapeGeometries.size() > 0); removeShape(); @@ -1723,6 +1769,17 @@ void setEditShapeImpl_(EditShape shape, int inputMode, if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) return; + if (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)) { + if (!check_structure_after_dirty_sweep_())// checks the edges. + { + m_dirty_check_failed = true;// set m_dirty_check_failed when an + // issue is found. We'll rerun the + // planesweep using robust crack and + // cluster approach. + return; + } + } + buildChains_(inputMode); if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) return; @@ -1847,10 +1904,11 @@ void removeShape() { if (m_shape == null) return; - if (m_geometryIDIndex != -1) + if (m_geometryIDIndex != -1) { m_shape.removeGeometryUserIndex(m_geometryIDIndex); + m_geometryIDIndex = -1; + } - m_geometryIDIndex = -1; if (m_clusterIndex != -1) { m_shape.removeUserIndex(m_clusterIndex); m_clusterIndex = -1; @@ -2454,13 +2512,6 @@ int compareEdgeAngles_(int edge1, int edge2) { Point2D pt10 = new Point2D(); getHalfEdgeFromXY(edge1, pt10); - // #ifdef DEBUG - // { - // Point_2D pt20; - // get_half_edge_from_xy(edge2, pt20); - // assert(pt10.is_equal(pt20)); - // } - // #endif Point2D v_1 = new Point2D(); v_1.sub(pt_1, pt10); @@ -2469,4 +2520,99 @@ int compareEdgeAngles_(int edge1, int edge2) { int result = Point2D._compareVectors(v_1, v_2); return result; } + + int compareEdgeAnglesForPair_(int edge1, int edge2) { + if (edge1 == edge2) + return 0; + + Point2D pt_1 = new Point2D(); + getHalfEdgeToXY(edge1, pt_1); + + Point2D pt_2 = new Point2D(); + getHalfEdgeToXY(edge2, pt_2); + + if (pt_1.isEqual(pt_2)) + return 0;// overlap case + + Point2D pt10 = new Point2D(); + getHalfEdgeFromXY(edge1, pt10); + + Point2D v_1 = new Point2D(); + v_1.sub(pt_1, pt10); + Point2D v_2 = new Point2D(); + v_2.sub(pt_2, pt10); + + if (v_2.y >= 0 && v_1.y > 0) { + int result = Point2D._compareVectors(v_1, v_2); + return result; + } + else { + return 0; + } + } + + boolean check_structure_after_dirty_sweep_() { + // for each cluster go through the cluster half edges and check that + // min(edge1_length, edge2_length) * angle_between is less than + // m_check_dirty_planesweep_tolerance. + // Doing this helps us weed out cases missed by the dirty plane sweep. + // We do not need absolute accuracy here. + assert (!m_dirty_check_failed); + assert (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)); + double sqr_tol = MathUtils.sqr(m_check_dirty_planesweep_tolerance); + Point2D pt10 = new Point2D(); + Point2D pt_2 = new Point2D(); + Point2D pt_1 = new Point2D(); + Point2D v_1 = new Point2D(); + Point2D v_2 = new Point2D(); + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + int first = getClusterHalfEdge(cluster); + if (first != -1) { + int edge = first; + getHalfEdgeFromXY(edge, pt10); + getHalfEdgeToXY(edge, pt_2); + v_2.sub(pt_2, pt10); + double sqr_len2 = v_2.sqrLength(); + + do { + int prev = edge; + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + + if (edge != prev) { + getHalfEdgeToXY(edge, pt_1); + assert (!pt_1.isEqual(pt_2)); + v_1.sub(pt_1, pt10); + double sqr_len1 = v_1.sqrLength(); + + double cross = v_1.crossProduct(v_2); // cross_prod = + // len1 * len2 * + // sinA => sinA + // = cross_prod + // / (len1 * + // len2); + double sqr_sinA = (cross * cross) + / (sqr_len1 * sqr_len2); // sqr_sinA is + // approximately A^2 + // especially for + // smaller angles + double sqr_dist = Math.min(sqr_len1, sqr_len2) + * sqr_sinA; + if (sqr_dist <= sqr_tol) { + // these edges incident on the cluster form a narrow + // wedge and thei require cracking event that was + // missed. + return false; + } + + v_2.setCoords(v_1); + sqr_len2 = sqr_len1; + pt_2.setCoords(pt_1); + } + } while (edge != first); + } + } + + return true; + } + } diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index 4e2b02b7..b6746704 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -24,7 +24,9 @@ package com.esri.core.geometry; import com.esri.core.geometry.AttributeStreamOfInt32.IntComparator; +import com.esri.core.geometry.Geometry.GeometryType; import com.esri.core.geometry.MultiVertexGeometryImpl.GeometryXSimple; + import java.util.ArrayList; final class TopologicalOperations { @@ -88,13 +90,14 @@ void setEditShape(EditShape shape, ProgressTracker progressTracker) { void setEditShapeCrackAndCluster(EditShape shape, double tolerance, ProgressTracker progressTracker) { - CrackAndCluster.execute(shape, tolerance, progressTracker); + CrackAndCluster.execute(shape, tolerance, progressTracker, true); for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape .getNextGeometry(geometry)) { if (shape.getGeometryType(geometry) == Geometry.Type.Polygon .value()) - Simplificator.execute(shape, geometry, -1, m_bOGCOutput); + Simplificator.execute(shape, geometry, -1, m_bOGCOutput, progressTracker); } + setEditShape(shape, progressTracker); } @@ -294,7 +297,7 @@ private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometry, - MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); return newGeometry; } @@ -513,7 +516,7 @@ int[] topoOperationPolygonPolygonEx_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForClusters(visitedClusters); m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometryPolygon, - MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); int[] result = new int[3];// always returns size 3 result. result[0] = newGeometryMultipoint; @@ -1150,11 +1153,16 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, // complications. Need to do // full crack and cluster. { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + CrackAndCluster.execute(shape, tolerance, progress_tracker, true); + dirty_result = false; + } else { + m_topo_graph.check_dirty_planesweep(tolerance); } } else { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + CrackAndCluster.execute(shape, tolerance, progress_tracker, true); + dirty_result = false; } + if (!b_use_winding_rule_for_polygons || shape.getGeometryType(geom) == Geometry.Type.MultiPoint .value()) @@ -1162,6 +1170,22 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, else m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + if (m_topo_graph.dirty_check_failed()) { + // we ran the sweep_vertical() before and it produced some + // issues that where detected by topo graph only. + assert (dirty_result); + m_topo_graph.removeShape(); + m_topo_graph = null; + // that's at most two level recursion + return planarSimplify(shape, geom, tolerance, + b_use_winding_rule_for_polygons, false, + progress_tracker); + } else { + //can proceed + } + + m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); + int ID_a = m_topo_graph.getGeometryID(geom); initMaskLookupArray_((ID_a) + 1); m_mask_lookup[ID_a] = true; // Works only when there is a single @@ -1175,9 +1199,11 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, .value())) { // geom can be a polygon or a polyline. // It can be a polyline only when the winding rule is true. + shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); Polygon polygon = (Polygon) shape.getGeometry(resGeom); + polygon.setFillRule(Polygon.FillRule.enumFillRuleOddEven);//standardize the fill rule. if (!dirty_result) { ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( GeometryXSimple.Strong, tolerance, false); @@ -1227,6 +1253,57 @@ static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, use_winding_rule_for_polygons, dirty_result, progress_tracker); } + boolean planarSimplifyNoCrackingAndCluster(boolean OGCoutput, EditShape shape, int geom, ProgressTracker progress_tracker) + { + m_bOGCOutput = OGCoutput; + m_topo_graph = new TopoGraph(); + int rule = shape.getFillRule(geom); + int gt = shape.getGeometryType(geom); + if (rule != Polygon.FillRule.enumFillRuleWinding || gt == GeometryType.MultiPoint) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + + if (m_topo_graph.dirty_check_failed()) + return false; + + m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); + + int ID_a = m_topo_graph.getGeometryID(geom); + initMaskLookupArray_((ID_a)+1); + m_mask_lookup[ID_a] = true; //Works only when there is a single geometry in the edit shape. + //To make it work when many geometries are present, this need to be modified. + + if (shape.getGeometryType(geom) == GeometryType.Polygon || (rule == Polygon.FillRule.enumFillRuleWinding && shape.getGeometryType(geom) != GeometryType.MultiPoint)) + { + //geom can be a polygon or a polyline. + //It can be a polyline only when the winding rule is true. + shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } + else if (shape.getGeometryType(geom) == GeometryType.Polyline) + { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } + else if (shape.getGeometryType(geom) == GeometryType.MultiPoint) + { + int resGeom = topoOperationMultiPoint_(); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } + else + { + throw new GeometryException("internal error"); + } + + return true; + } + + static MultiVertexGeometry simplifyOGC(MultiVertexGeometry input_geom, double tolerance, boolean dirty_result, ProgressTracker progress_tracker) { TopologicalOperations topoOps = new TopologicalOperations(); diff --git a/src/test/java/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java index 94bd5fb2..173a4970 100644 --- a/src/test/java/com/esri/core/geometry/TestEditShape.java +++ b/src/test/java/com/esri/core/geometry/TestEditShape.java @@ -183,7 +183,7 @@ public static void testEditShape() { EditShape editShape = new EditShape(); int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true); + editShape.filterClosePoints(0.002, true, false); Polygon poly2 = (Polygon) editShape.getGeometry(geom); assertTrue(poly2.isEmpty()); } @@ -197,7 +197,7 @@ public static void testEditShape() { EditShape editShape = new EditShape(); int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true); + editShape.filterClosePoints(0.002, true, false); Polygon poly2 = (Polygon) editShape.getGeometry(geom); assertTrue(!poly2.isEmpty()); } @@ -211,7 +211,7 @@ public static void testEditShape() { EditShape editShape = new EditShape(); int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true); + editShape.filterClosePoints(0.002, true, false); Polygon poly2 = (Polygon) editShape.getGeometry(geom); assertTrue(poly2.isEmpty()); } @@ -295,7 +295,7 @@ public static void testEditShape() { EditShape shape = new EditShape(); int g1 = shape.addGeometry(line1); int g2 = shape.addGeometry(line2); - CrackAndCluster.execute(shape, 0.001, null); + CrackAndCluster.execute(shape, 0.001, null, true); Polyline chopped_line1 = (Polyline) shape.getGeometry(g1); Polyline chopped_line2 = (Polyline) shape.getGeometry(g2); diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index d377e6f6..0cdbf6f7 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; public class TestGeodetic extends TestCase { @@ -15,7 +16,7 @@ protected void tearDown() throws Exception { } @Test - public static void testTriangleLength() { + public void testTriangleLength() { Point pt_0 = new Point(10, 10); Point pt_1 = new Point(20, 20); Point pt_2 = new Point(20, 10); @@ -27,7 +28,7 @@ public static void testTriangleLength() { } @Test - public static void testRotationInvariance() { + public void testRotationInvariance() { Point pt_0 = new Point(10, 40); Point pt_1 = new Point(20, 60); Point pt_2 = new Point(20, 40); @@ -50,7 +51,7 @@ public static void testRotationInvariance() { } @Test - public static void testLengthAccurateCR191313() { + public void testLengthAccurateCR191313() { /* * // random_test(); OperatorFactoryLocal engine = * OperatorFactoryLocal.getInstance(); //TODO: Make this: @@ -69,4 +70,4 @@ public static void testLengthAccurateCR191313() { * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); */ } - } + } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 2de57b43..ce4f81dc 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -25,20 +25,18 @@ protected void tearDown() throws Exception { SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); @Test - public void testGeomToJSonExportSRFromWkiOrWkt_CR181369() + public void testLocalExport() throws JsonParseException, IOException { - testPoint(); - testPolyline(); - testPolygon(); - testEnvelope(); - testMultiPoint(); - testCR181369(); - // These tests return the result of a method called - // checkResultSpatialRef. - // However, the tests pass or fail regardless of what that method - // returns. + String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); + //assertTrue(s.contains(".")); + //assertFalse(s.contains(",")); + Polyline line = new Polyline(); + line.startPath(1.1, 2.2); + line.lineTo(2.3, 4.5); + String s1 = OperatorExportToJson.local().execute(null, line); + assertTrue(s.contains(".")); } - + boolean testPoint() throws JsonParseException, IOException { boolean bAnswer = true; Point point1 = new Point(10.0, 20.0); diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index df53b170..0140198a 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -23,6 +23,8 @@ public void testPt() { assertTrue(pt.isEmpty()); pt.setXY(10, 2); assertFalse(pt.isEmpty()); + + pt.toString(); } @Test @@ -165,5 +167,21 @@ public void testEnvelope2D_corners() { assertFalse(env.containsExclusive(env.getUpperLeft())); assertTrue(env.contains(env.getUpperLeft())); assertTrue(env.containsExclusive(env.getCenter())); + } + + @Test + public void testReplaceNaNs() { + Envelope env = new Envelope(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + pt.queryEnvelope(env); + pt.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(pt.equals(new Point(1, 2, 5))); + + assertTrue(env.hasZ()); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).isEmpty()); + env.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).equals(new Envelope1D(5, 5))); } } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 22d9617c..2d6906ab 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; import com.esri.core.geometry.ogc.OGCGeometry; @@ -66,8 +67,9 @@ public void testCreation1() { @SuppressWarnings("unused") int number = poly.getStateFlag(); Envelope env = new Envelope(1000, 2000, 1010, 2010); - + env.toString(); poly.addEnvelope(env, false); + poly.toString(); number = poly.getStateFlag(); assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); assertTrue(Math.abs(poly.calculateLength2D() - 40) < 1e-12); @@ -1074,8 +1076,12 @@ public void testCR177477getPathEnd() { // int endIndex = pg.getPathEnd(pathCount - 1); Line line = new Line(); + line.toString(); + line.setStart(new Point(0, 0)); line.setEnd(new Point(1, 0)); + + line.toString(); double geoLength = GeometryEngine.geodesicDistanceOnWGS84(new Point(0, 0), new Point(1, 0)); @@ -1138,4 +1144,40 @@ public void testBoundary() { assertTrue(s .equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); } + + @Test + public void testReplaceNaNs() { + { + MultiPoint mp = new MultiPoint(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + mp.add(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setZ(3); + mp.add(pt); + + mp.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); + assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); + } + + { + Polygon mp = new Polygon(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + mp.startPath(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setZ(3); + mp.lineTo(pt); + + mp.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); + assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); + } + } + } diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index 3836ec13..7ffdc06d 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -1,6 +1,9 @@ package com.esri.core.geometry; +import java.util.ArrayList; + import junit.framework.TestCase; + import org.junit.Test; public class TestQuadTree extends TestCase { @@ -78,6 +81,7 @@ public static void test2() { assertTrue(count == 10000); } + public static Polyline makePolyline() { Polyline poly = new Polyline(); diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 613f8b13..10386f82 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -19,7 +19,7 @@ protected void tearDown() throws Exception { } @Test - public static void testCreation() { + public void testCreation() { { OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); SpatialReference inputSR = SpatialReference.create(3857); @@ -132,7 +132,7 @@ public static void testCreation() { } @Test - public static void testOperatorDisjoint() { + public void testOperatorDisjoint() { { OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); SpatialReference inputSR = SpatialReference.create(3857); @@ -191,7 +191,7 @@ public static void testOperatorDisjoint() { } @Test - public static void testTouchPointLineCR183227() {// Tests CR 183227 + public void testTouchPointLineCR183227() {// Tests CR 183227 OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); @@ -219,7 +219,7 @@ public static void testTouchPointLineCR183227() {// Tests CR 183227 } @Test - public static void testTouchPointLineClosed() {// Tests CR 183227 + public void testTouchPointLineClosed() {// Tests CR 183227 OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); @@ -247,7 +247,7 @@ public static void testTouchPointLineClosed() {// Tests CR 183227 } @Test - public static void testTouchPolygonPolygon() { + public void testTouchPolygonPolygon() { OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); @@ -270,7 +270,7 @@ public static void testTouchPolygonPolygon() { } @Test - public static void testContainsFailureCR186456() { + public void testContainsFailureCR186456() { { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); @@ -283,7 +283,7 @@ public static void testContainsFailureCR186456() { } @Test - public static void testWithin() { + public void testWithin() { { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); @@ -364,7 +364,7 @@ public static void testWithin() { } @Test - public static void testContains() { + public void testContains() { { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); @@ -444,7 +444,7 @@ public static void testContains() { } @Test - public static void testOverlaps() { + public void testOverlaps() { {// empty polygon OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); @@ -618,7 +618,7 @@ public static void testOverlaps() { } @Test - public static void testPolygonPolygonEquals() { + public void testPolygonPolygonEquals() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); SpatialReference sr = SpatialReference.create(102100); @@ -697,7 +697,7 @@ public static void testPolygonPolygonEquals() { } @Test - public static void testMultiPointMultiPointEquals() { + public void testMultiPointMultiPointEquals() { OperatorEquals equals = (OperatorEquals) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals); SpatialReference sr = SpatialReference.create(102100); @@ -738,7 +738,7 @@ public static void testMultiPointMultiPointEquals() { } @Test - public static void testMultiPointPointEquals() { + public void testMultiPointPointEquals() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal @@ -780,7 +780,7 @@ public static void testMultiPointPointEquals() { } @Test - public static void testPointPointEquals() { + public void testPointPointEquals() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal @@ -843,7 +843,7 @@ public static void testPointPointEquals() { } @Test - public static void testPolygonPolygonDisjoint() { + public void testPolygonPolygonDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -916,10 +916,61 @@ public static void testPolygonPolygonDisjoint() { assertTrue(!res); res = disjoint.execute(polygon2, polygon1, sr, null); assertTrue(!res); + + polygon1 = (Polygon)OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1.reverseAllPaths(); + polygon2.reverseAllPaths(); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 contains polygon2, but polygon2 is counterclockwise. + str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]],[[11,0],[11,10],[21,10],[21,0],[11,0]]]}"; + str2 = "{\"rings\":[[[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1 = (Polygon)OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,20],[0,30],[10,30],[10,20],[0,20]],[[20,20],[20,30],[30,30],[30,20],[20,20]],[[20,0],[20,10],[30,10],[30,0],[20,0]]]}"; + str2 = "{\"rings\":[[[14,14],[14,16],[16,16],[16,14],[14,14]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + polygon1 = (Polygon)OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); } @Test - public static void testPolylinePolylineDisjoint() { + public void testPolylinePolylineDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -970,7 +1021,7 @@ public static void testPolylinePolylineDisjoint() { } @Test - public static void testPolygonPolylineDisjoint() { + public void testPolygonPolylineDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1031,7 +1082,7 @@ public static void testPolygonPolylineDisjoint() { } @Test - public static void testPolylineMultiPointDisjoint() { + public void testPolylineMultiPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1077,7 +1128,7 @@ public static void testPolylineMultiPointDisjoint() { } @Test - public static void testPolylinePointDisjoint() { + public void testPolylinePointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -1136,7 +1187,7 @@ public static void testPolylinePointDisjoint() { } @Test - public static void testMultiPointMultiPointDisjoint() { + public void testMultiPointMultiPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1178,7 +1229,7 @@ public static void testMultiPointMultiPointDisjoint() { } @Test - public static void testMultiPointPointDisjoint() { + public void testMultiPointPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -1226,7 +1277,7 @@ public static void testMultiPointPointDisjoint() { } @Test - public static void testPolygonMultiPointDisjoint() { + public void testPolygonMultiPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1275,7 +1326,7 @@ public static void testPolygonMultiPointDisjoint() { } @Test - public static void testPolygonMultiPointTouches() { + public void testPolygonMultiPointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1316,7 +1367,7 @@ public static void testPolygonMultiPointTouches() { } @Test - public static void testPolygonPointTouches() { + public void testPolygonPointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1348,7 +1399,7 @@ public static void testPolygonPointTouches() { } @Test - public static void testPolygonPolygonTouches() { + public void testPolygonPolygonTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1430,7 +1481,7 @@ public static void testPolygonPolygonTouches() { } @Test - public static void testPolygonPolylineTouches() { + public void testPolygonPolylineTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1495,7 +1546,7 @@ public static void testPolygonPolylineTouches() { } @Test - public static void testPolylinePolylineTouches() { + public void testPolylinePolylineTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1640,7 +1691,7 @@ public static void testPolylinePolylineTouches() { } @Test - public static void testPolylineMultiPointTouches() { + public void testPolylineMultiPointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1699,7 +1750,7 @@ public static void testPolylineMultiPointTouches() { } @Test - public static void testPolylineMultiPointCrosses() { + public void testPolylineMultiPointCrosses() { OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Crosses)); SpatialReference sr = SpatialReference.create(102100); @@ -1752,7 +1803,7 @@ public static void testPolylineMultiPointCrosses() { } @Test - public static void testPolylinePointTouches() { + public void testPolylinePointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1778,7 +1829,7 @@ public static void testPolylinePointTouches() { } @Test - public static void testPolygonPolygonOverlaps() { + public void testPolygonPolygonOverlaps() { OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); SpatialReference sr = SpatialReference.create(102100); @@ -1855,7 +1906,7 @@ public static void testPolygonPolygonOverlaps() { } @Test - public static void testPolygonPolylineWithin() { + public void testPolygonPolylineWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -1888,7 +1939,7 @@ public static void testPolygonPolylineWithin() { } @Test - public static void testMultiPointMultiPointWithin() { + public void testMultiPointMultiPointWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -1940,7 +1991,7 @@ public static void testMultiPointMultiPointWithin() { } @Test - public static void testPolylinePolylineOverlaps() { + public void testPolylinePolylineOverlaps() { OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); SpatialReference sr = SpatialReference.create(102100); @@ -2007,7 +2058,7 @@ public static void testPolylinePolylineOverlaps() { } @Test - public static void testMultiPointMultiPointOverlaps() { + public void testMultiPointMultiPointOverlaps() { OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); SpatialReference sr = SpatialReference.create(102100); @@ -2065,7 +2116,7 @@ public static void testMultiPointMultiPointOverlaps() { } @Test - public static void testPolygonPolygonWithin() { + public void testPolygonPolygonWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -2120,10 +2171,140 @@ public static void testPolygonPolygonWithin() { res = within.execute(polygon2, polygon1, sr, null); assertTrue(!res); + str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0]],[[12,8],[12,10],[18,10],[18,8],[12,8]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2]],[[12,2],[12,4],[18,4],[18,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Same as above, but winding fill rule + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + polygon1.setFillRule(Polygon.FillRule.enumFillRuleWinding); + polygon2.setFillRule(Polygon.FillRule.enumFillRuleWinding); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[11,11],[11,20],[20,20],[20,11],[11,11]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[9.9999999925,4],[9.9999999925,6],[10.0000000075,6],[10.0000000075,4],[9.9999999925,4]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + res = OperatorOverlaps.local().execute(polygon1, polygon2, sr, null); + assertTrue(!res); + + res = OperatorTouches.local().execute(polygon1, polygon2, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]],[[15,5],[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[2,2],[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]],[[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5],[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]],[[15,5],[15,6]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2],[2,2],[2,2]],[[15,5],[15,6]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); } @Test - public static void testPolylinePolylineWithin() { + public void testPolylinePolylineWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -2191,7 +2372,7 @@ public static void testPolylinePolylineWithin() { } @Test - public static void testPolylineMultiPointWithin() { + public void testPolylineMultiPointWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -2239,7 +2420,7 @@ public static void testPolylineMultiPointWithin() { } @Test - public static void testPolygonMultiPointWithin() { + public void testPolygonMultiPointWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -2270,7 +2451,7 @@ public static void testPolygonMultiPointWithin() { } @Test - public static void testPolygonPolylineCrosses() { + public void testPolygonPolylineCrosses() { OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Crosses)); SpatialReference sr = SpatialReference.create(102100); @@ -2324,7 +2505,7 @@ public static void testPolygonPolylineCrosses() { } @Test - public static void testPolylinePolylineCrosses() { + public void testPolylinePolylineCrosses() { OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Crosses)); SpatialReference sr = SpatialReference.create(102100); @@ -2414,7 +2595,7 @@ public static void testPolylinePolylineCrosses() { } @Test - public static void testPolygonEnvelope() { + public void testPolygonEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -2866,7 +3047,7 @@ public static void testPolygonEnvelope() { } @Test - public static void testPolylineEnvelope() { + public void testPolylineEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -3254,7 +3435,7 @@ public static void testPolylineEnvelope() { } @Test - public static void testMultiPointEnvelope() { + public void testMultiPointEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -3583,7 +3764,7 @@ public static void testMultiPointEnvelope() { } @Test - public static void testPointEnvelope() { + public void testPointEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -3722,7 +3903,7 @@ public static void testPointEnvelope() { } @Test - public static void testEnvelopeEnvelope() { + public void testEnvelopeEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -4350,7 +4531,7 @@ static void wiggleGeometry(Geometry geometry, double tolerance, int rand) { } @Test - public static void testDisjointRelationFalse() { + public void testDisjointRelationFalse() { { OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); @@ -4425,259 +4606,341 @@ public static void testDisjointRelationFalse() { } @Test - public static void testPolylinePolylineRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; + public void testPolylinePolylineRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; - Polyline polyline1 = new Polyline(); - Polyline polyline2 = new Polyline(); + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); - polyline1.startPath(0, 0); - polyline1.lineTo(1, 1); + polyline1.startPath(0, 0); + polyline1.lineTo(1, 1); - polyline2.startPath(1, 1); - polyline2.lineTo(2, 0); + polyline2.startPath(1, 1); + polyline2.lineTo(2, 0); - scl = "FF1FT01T2"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "FF1FT01T2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "****TF*T*"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "****TF*T*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "****F****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "****F****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "**1*0*T**"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "**1*0*T**"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "****1****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "****1****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "**T*001*T"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "**T*001*T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "T********"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "T********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "F********"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "F********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(1, 0); + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); - polyline2.startPath(0, 0); - polyline2.lineTo(1, 0); + polyline2.startPath(0, 0); + polyline2.lineTo(1, 0); - scl = "1FFFTFFFT"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "1FFFTFFFT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "1*T*T****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "1*T*T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "1T**T****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "1T**T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(0.5, 0.5); - polyline1.lineTo(1, 1); + polyline1.startPath(0, 0); + polyline1.lineTo(0.5, 0.5); + polyline1.lineTo(1, 1); - polyline2.startPath(1, 0); - polyline2.lineTo(0.5, 0.5); - polyline2.lineTo(0, 1); + polyline2.startPath(1, 0); + polyline2.lineTo(0.5, 0.5); + polyline2.lineTo(0, 1); - scl = "0F1FFTT0T"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "0F1FFTT0T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "*T*******"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "*T*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "*F*F*****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "*F*F*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(1, 0); + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); - polyline2.startPath(1, -1); - polyline2.lineTo(1, 1); + polyline2.startPath(1, -1); + polyline2.lineTo(1, 1); - scl = "FT1TF01TT"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "FT1TF01TT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "***T*****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "***T*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(0, 20); - polyline1.lineTo(20, 20); - polyline1.lineTo(20, 0); - polyline1.lineTo(0, 0); // has no boundary + polyline1.startPath(0, 0); + polyline1.lineTo(0, 20); + polyline1.lineTo(20, 20); + polyline1.lineTo(20, 0); + polyline1.lineTo(0, 0); // has no boundary - polyline2.startPath(3, 3); - polyline2.lineTo(5, 5); + polyline2.startPath(3, 3); + polyline2.lineTo(5, 5); + + op.accelerateGeometry(polyline1, sr, Geometry.GeometryAccelerationDegree.enumHot); - op.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); + scl = "FF1FFF102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "FF1FFF102"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); - polyline1.startPath(4, 0); - polyline1.lineTo(0, 4); - polyline1.lineTo(4, 8); - polyline1.lineTo(8, 4); + polyline2.startPath(8, 1); + polyline2.lineTo(8, 2); - polyline2.startPath(8, 1); - polyline2.lineTo(8, 2); + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); - op.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); + scl = "FF1FF0102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "FF1FF0102"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline2.startPath(3, 2); + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + scl = "******0F*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + scl = "******0F*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "******0F*"; + + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(3, 3); + polyline1.lineTo(3, 4); + polyline1.lineTo(3, 3); + polyline2.startPath(1, 1); + polyline2.lineTo(1, 1); + + scl = "FF1FFF0F2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "FF0FFF1F2"; + res = op.execute(polyline2, polyline1, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline2.startPath(2, 2); + polyline2.lineTo(2, 2); + + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.lineTo(2, 2); + + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); } @Test - public static void testPolygonPolylineRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polygon polygon1 = new Polygon(); - Polyline polyline2 = new Polyline(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polyline2.startPath(-10, 0); - polyline2.lineTo(0, 0); - - scl = "FF2F01102"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "**1*0110*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "T***T****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "FF*FT****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(0, 0); - polyline2.lineTo(10, 0); - - scl = "***1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "F**1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "0**1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "F**1*1TF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline2.setEmpty(); - polyline2.startPath(1, 1); - polyline2.lineTo(5, 5); - - scl = "TT*******"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1T2FF1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1T1FF1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline2.setEmpty(); - polyline2.startPath(5, 5); - polyline2.lineTo(15, 5); - - scl = "1T*0F*T0T"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - - polygon1.startPath(2, 0); - polygon1.lineTo(0, 2); - polygon1.lineTo(2, 4); - polygon1.lineTo(4, 2); - - polyline2.startPath(1, 2); - polyline2.lineTo(3, 2); - - op.accelerateGeometry(polygon1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - scl = "TTTFF****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(5, 2); - polyline2.lineTo(7, 2); - scl = "FF2FFT***"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); + public void testPolygonPolylineRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(-10, 0); + polyline2.lineTo(0, 0); + + scl = "FF2F01102"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "**1*0110*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "T***T****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "FF2FT****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(0, 0); + polyline2.lineTo(10, 0); + + scl = "**21*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "F*21*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "0**1*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "F**1*1TF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(1, 1); + polyline2.lineTo(5, 5); + + scl = "TT2******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T2FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T1FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(5, 5); + polyline2.lineTo(15, 5); + + scl = "1T20F*T0T"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(2, 0); + polygon1.lineTo(0, 2); + polygon1.lineTo(2, 4); + polygon1.lineTo(4, 2); + + polyline2.startPath(1, 2); + polyline2.lineTo(3, 2); + + op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + scl = "TTTFF****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(5, 2); + polyline2.lineTo(7, 2); + scl = "FF2FFT***"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(1, 0); + polyline2.startPath(0, 10); + polyline2.lineTo(0, 9); + polyline2.startPath(10, 0); + polyline2.lineTo(9, 0); + polyline2.startPath(0, -10); + polyline2.lineTo(0, -9); + scl = "**2******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(0, 0); + polyline2.startPath(0, 10); + polyline2.lineTo(0, 9); + scl = "**1******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); } @Test - public static void testPolygonPolygonRelate() { + public void testPolygonPolygonRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4724,10 +4987,46 @@ public static void testPolygonPolygonRelate() { scl = "212FF1FFT"; res = op.execute(polygon1, polygon2, sr, scl, null); assertTrue(res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + polygon1.startPath(3, 3); + polygon1.lineTo(3, 4); + polygon1.lineTo(3, 3); + polygon2.startPath(1, 1); + polygon2.lineTo(1, 1); + + scl = "FF1FFF0F2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + scl = "FF0FFF1F2"; + res = op.execute(polygon2, polygon1, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 100); + polygon1.lineTo(100, 100); + polygon1.lineTo(100, 0); + polygon2.startPath(50, 50); + polygon2.lineTo(50, 50); + polygon2.lineTo(50, 50); + + op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + + scl = "0F2FF1FF2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + polygon2.lineTo(51, 50); + scl = "1F2FF1FF2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); } @Test - public static void testMultiPointPointRelate() { + public void testMultiPointPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4751,10 +5050,19 @@ public static void testMultiPointPointRelate() { m1.add(1, 1); res = op.execute(m1, p2, sr, scl, null); assertTrue(res); + + m1.setEmpty(); + + m1.add(1, 1); + m1.add(2, 2); + + scl = "FF0FFFTF2"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); } @Test - public static void testPointPointRelate() { + public void testPointPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4775,10 +5083,22 @@ public static void testPointPointRelate() { p2.setXY(1, 0); res = op.execute(p1, p2, null, scl, null); assertTrue(!res); + + p1.setEmpty(); + p2.setEmpty(); + scl = "*********"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(res); + scl = "FFFFFFFFF"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(res); + scl = "FFFFFFFFT"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(!res); } @Test - public static void testPolygonMultiPointRelate() { + public void testPolygonMultiPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4858,7 +5178,7 @@ public static void testPolygonMultiPointRelate() { } @Test - public static void testPolygonPointRelate() { + public void testPolygonPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4878,42 +5198,147 @@ public static void testPolygonPointRelate() { scl = "FF20FTFFT"; res = op.execute(polygon, point, sr, scl, null); assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + scl = "0FFFFFFF2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 1); + polygon.lineTo(0, 0); + scl = "0F1FFFFF2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + point.setXY(-1, 0); + + scl = "FF1FFF0F2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + scl = "FF1FFFTFT"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + scl = "FF0FFF0F2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); } @Test - public static void testPolylineMultiPointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; + public void testPolylineMultiPointRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); - polyline1.startPath(0, 0); - polyline1.lineTo(10, 0); + polyline1.startPath(0, 0); + polyline1.lineTo(10, 0); - multipoint2.add(0, 0); - multipoint2.add(5, 5); + multipoint2.add(0, 0); + multipoint2.add(5, 5); - scl = "FF10F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "FF10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - multipoint2.add(5, 0); + multipoint2.add(5, 0); - scl = "0F10F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "0F10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - scl = "0F11F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(!res); + scl = "0F11F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(!res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); + polyline1.lineTo(4, 0); // has no boundary + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + + scl = "FF1FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(4, 0); + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + scl = "FF0FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint2.add(-2, 0); + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(10, 10); + polyline1.lineTo(10, 10); + multipoint2.add(10, 10); + + scl = "0FFFFFFF2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.startPath(12, 12); + polyline1.lineTo(12, 12); + + scl = "0F0FFFFF2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(10, 10); + polyline1.lineTo(10, 10); + multipoint2.add(0, 0); + + scl = "FF0FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); } @Test - public static void testMultiPointMultipointRelate() { + public void testMultiPointMultipointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4945,10 +5370,101 @@ public static void testMultiPointMultipointRelate() { res = GeometryEngine.relate(multipoint1, multipoint2, sr, scl); assertTrue(res); + + multipoint1.setEmpty(); + multipoint2.setEmpty(); + + multipoint1.add(0, 0); + multipoint2.add(1, 1); + + scl = "FFTFFF0FT"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); } + @Test + public void testPolylinePointRelate() + { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline = new Polyline(); + Point point = new Point(); + + polyline.startPath(0, 2); + polyline.lineTo(0, 4); + + point.setXY(0, 3); + + scl = "0F1FF0FF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(1, 3); + + scl = "FF1FF00F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.lineTo(4, 4); + polyline.lineTo(4, 2); + polyline.lineTo(0, 2); // no bounadry + point.setXY(0, 3); + + scl = "0F1FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + scl = "0F1FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(1, 3); + + scl = "FF1FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(10, 10); + + scl = "FF1FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.setEmpty(); + point.setEmpty(); + + polyline.startPath(10, 10); + polyline.lineTo(10, 10); + point.setXY(10, 10); + + scl = "0FFFFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.startPath(12, 12); + polyline.lineTo(12, 12); + + scl = "0F0FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.setEmpty(); + point.setEmpty(); + + polyline.startPath(10, 10); + polyline.lineTo(10, 10); + point.setXY(0, 0); + + scl = "FF0FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + } + @Test - public static void testCrosses_github_issue_40() { + public void testCrosses_github_issue_40() { // Issue 40: Acceleration without a spatial reference changes the result // of relation operators Geometry geom1 = OperatorImportFromWkt.local().execute(0, diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index b7caf8a3..bdc2be0f 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -10,6 +10,7 @@ import junit.framework.TestCase; import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; import org.junit.Test; public class TestSimplify extends TestCase { @@ -1325,5 +1326,21 @@ public void testPolylineIsSimpleForOGC() throws IOException { } } + + @Test + public void testFillRule() throws JsonParseException, IOException { + //self intersecting star shape + MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); + Polygon poly = (Polygon)mg.getGeometry(); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); + Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); + assertTrue(((Polygon)simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + //solid start without holes: + MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); + boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); + assertTrue(equals); + } } From fc13f7f68c7fd814c1c9dae8a1c7239700765d6d Mon Sep 17 00:00:00 2001 From: serg4066 Date: Tue, 16 Dec 2014 11:09:40 -0800 Subject: [PATCH 002/116] minor fixes --- .../com/esri/core/geometry/MathUtils.java | 2 +- .../geometry/MultiVertexGeometryImpl.java | 3 +- .../geometry/OperatorImportFromESRIShape.java | 12 +- .../core/geometry/RelationalOperations.java | 230 ++++++++++-------- .../com/esri/core/geometry/TestPolygon.java | 21 ++ 5 files changed, 159 insertions(+), 109 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index 8ed61457..a338dccd 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -173,7 +173,7 @@ static double lerp(double start_, double end_, double t) { else v = end_ - (end_ - start_) * (1.0 - t); - assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_)); + assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_) || NumberUtils.isNaN(start_) || NumberUtils.isNaN(end_)); return v; } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 6fb511ea..9fbe1756 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -1078,8 +1078,7 @@ public void replaceNaNs(int semantics, double value) { boolean modified = false; int ncomps = VertexDescription.getComponentCount(semantics); for (int i = 0; i < ncomps; i++) { - int attr = m_description.getAttributeIndex(semantics); - AttributeStreamBase streamBase = getAttributeStreamRef(attr); + AttributeStreamBase streamBase = getAttributeStreamRef(semantics); if (streamBase instanceof AttributeStreamOfDbl) { AttributeStreamOfDbl dblStream = (AttributeStreamOfDbl)streamBase; for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java index 32c7f778..be7bf6cc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java @@ -39,7 +39,11 @@ public Type getType() { /** * Performs the ImportFromESRIShape operation on a stream of shape buffers - * + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes from a trusted source and is topologically simple. + * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. + * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be Geometry.Type.Unknown if the type of geometry has to be + * figured out from the shape buffer. + * @param shapeBuffers The cursor over shape buffers that hold the Geometries in ESRIShape format. * @return Returns a GeometryCursor. */ abstract GeometryCursor execute(int importFlags, Geometry.Type type, @@ -47,8 +51,10 @@ abstract GeometryCursor execute(int importFlags, Geometry.Type type, /** * Performs the ImportFromESRIShape operation. - * @param importFlags Use the {@link ShapeImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes from a trusted source and is topologically simple. + * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. + * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be Geometry.Type.Unknown if the type of geometry has to be + * figured out from the shape buffer. * @param shapeBuffer The buffer holding the Geometry in ESRIShape format. * @return Returns the imported Geometry. */ diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 0658a8e5..34c84cf2 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1149,33 +1149,36 @@ private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; for (int i = 0; i < multipoint_b.getPointCount(); i++) { ptB = multipoint_b.getXY(i); - if (!env_a_inflated.contains(ptB)) - continue; + if (env_a_inflated.contains(ptB)) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); - if (result == PolygonUtils.PiPResult.PiPBoundary) - b_boundary = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - return false; + if (result == PolygonUtils.PiPResult.PiPBoundary) + b_boundary = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + return false; + } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } if (b_boundary) @@ -1209,19 +1212,9 @@ private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; for (int i = 0; i < multipoint_b.getPointCount(); i++) { @@ -1243,6 +1236,19 @@ else if (result == PolygonUtils.PiPResult.PiPInside) if (b_interior && b_exterior) return true; + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } return false; @@ -1277,19 +1283,9 @@ private static boolean polygonContainsMultiPoint_(Polygon polygon_a, MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; for (int i = 0; i < multipoint_b.getPointCount(); i++) { @@ -1304,6 +1300,19 @@ private static boolean polygonContainsMultiPoint_(Polygon polygon_a, b_interior = true; else if (result == PolygonUtils.PiPResult.PiPOutside) return false; + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } return b_interior; @@ -3180,38 +3189,16 @@ private static boolean polygonDisjointMultiPath_(Polygon polygon_a, return false; Polygon pa = null; - Polygon p_polygon_a = null; - - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPointCount()) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + Polygon p_polygon_a = polygon_a; Polygon pb = null; Polygon p_polygon_b = null; if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) - { - Polygon polygon_b = (Polygon)multipath_b; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) - { - pb = new Polygon(); - polygon_b.copyTo(pb); - ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_b = pb; - } - else - { - p_polygon_b = (Polygon)multipath_b; - } - } + p_polygon_b = (Polygon)multipath_b; + + boolean b_checked_polygon_a_quad_tree = false; + boolean b_checked_polygon_b_quad_tree = false; do { @@ -3244,6 +3231,37 @@ private static boolean polygonDisjointMultiPath_(Polygon polygon_a, return false; } } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPathCount() - 1) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + { + if (!b_checked_polygon_b_quad_tree) { + Polygon polygon_b = (Polygon) multipath_b; + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } else { + p_polygon_b = (Polygon) multipath_b; + } + + b_checked_polygon_b_quad_tree = true; + } + } + } while (intersector.next()); return true; @@ -4750,7 +4768,6 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu while (intersector.next()) { - b_boundaries_intersect = true; int vertex_a = intersector.getRedElement(); int vertex_b = intersector.getBlueElement(); @@ -4761,15 +4778,16 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, tolerance); - if (result == 1) - { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + if (result != 0) { + b_boundaries_intersect = true; + if (result == 1) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - { - b_result_known[0] = true; - return false; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) { + b_result_known[0] = true; + return false; + } } } } @@ -4785,19 +4803,9 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu env_a_inflated.inflate(tolerance, tolerance); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPointCount()) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; Envelope2D path_env_b = new Envelope2D(); @@ -4818,6 +4826,19 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu { return false; } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPathCount() - 1) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } } @@ -4833,19 +4854,9 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu env_b_inflated.inflate(tolerance, tolerance); Polygon pb = null; - Polygon p_polygon_b = null; + Polygon p_polygon_b = polygon_b; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) - { - pb = new Polygon(); - polygon_b.copyTo(pb); - ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_b = pb; - } - else - { - p_polygon_b = polygon_b; - } + boolean b_checked_polygon_b_quad_tree = false; Envelope2D path_env_a = new Envelope2D(); @@ -4862,6 +4873,19 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu if (res == 1) return false; } + + if (!b_checked_polygon_b_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } else { + p_polygon_b = polygon_b; + } + + b_checked_polygon_b_quad_tree = true; + } } } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 2d6906ab..f83d2569 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1178,6 +1178,27 @@ public void testReplaceNaNs() { assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); } + + { + Polygon mp = new Polygon(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setM(Double.NaN); + mp.startPath(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setM(3); + mp.lineTo(pt); + + mp.replaceNaNs(VertexDescription.Semantics.M, 5); + Point p = new Point(1, 2); p.setM(5); + boolean b = mp.getPoint(0).equals(p); + assertTrue(b); + p = new Point(11, 12); p.setM(3); + b = mp.getPoint(1).equals(p); + assertTrue(b); + } + } } From 9748d68e86192288066fb647ad021d2b68836afe Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Wed, 14 Jan 2015 10:36:11 -0800 Subject: [PATCH 003/116] removed debugging code --- .../core/geometry/RelationalOperationsMatrix.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 1ad87dc4..7660fc4e 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -383,21 +383,6 @@ else if (relation == RelationalOperations.Relation.within) edit_shape = new EditShape(); geom_a = edit_shape.addGeometry(polygon_simple_a); geom_b = edit_shape.addGeometry(boundary_b); - - int pc1 = polygon_simple_a.getPointCount(); - int pc2 = boundary_b.getPointCount(); - - for (int i = 0; i < pc2; i++) - { - Point2D p = boundary_b.getXY(i); - i = i; - } - - boolean isclosed0 = boundary_b.isClosedPath(0); - boolean isclosed1 = boundary_b.isClosedPath(1); - - String s1 = OperatorExportToJson.local().execute(null, polygon_simple_a); - String s2 = OperatorExportToJson.local().execute(null, boundary_b); relOps.setEditShape_(edit_shape, progress_tracker); // Check no interior lines of the boundary intersect the exterior From 2700750dcf061443d7fc5bec8098a1a57b5721ac Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 14 Jan 2015 12:31:10 -0800 Subject: [PATCH 004/116] fix Envelope.toString --- src/main/java/com/esri/core/geometry/Envelope.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 9d74440b..3f3653f1 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1139,7 +1139,7 @@ public String toString() { if (isEmpty()) return "Envelope: []"; - String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmin + ", " + m_envelope.ymin +"]"; + String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmax + ", " + m_envelope.ymax +"]"; return s; } From a6fb1645ea61a440bdaf7eaf2710715bf6fe84a7 Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Fri, 16 Jan 2015 08:39:16 -0800 Subject: [PATCH 005/116] Resize buffer to accomodate insertion --- .../com/esri/core/geometry/AttributeStreamOfDbl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 09f87ff4..2009eaf0 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -405,7 +405,14 @@ public void insertRange(int start, AttributeStreamBase src, int srcStart, if (!bForward && (stride < 1 || count % stride != 0)) throw new IllegalArgumentException(); - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + int excess_space = m_size - validSize; + + if (excess_space < count) { + int original_size = m_size; + resize(original_size + count - excess_space); + } + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - start); if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { From bfd52befa434b57d3502835cba533cfe49b47a91 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 6 Feb 2015 14:17:51 -0800 Subject: [PATCH 006/116] README: update copyright notice to 2015 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16d10710..03d91b99 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013 Esri +Copyright 2013-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From e59f9b16f3939a76202554887ce0d9aab2155296 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Fri, 6 Feb 2015 14:57:22 -0800 Subject: [PATCH 007/116] updated date for copyright to 2015. Changed SimpleRasterizer visibility --- .../core/geometry/AttributeStreamBase.java | 2 +- .../core/geometry/AttributeStreamOfDbl.java | 6 +- .../core/geometry/AttributeStreamOfFloat.java | 2 +- .../core/geometry/AttributeStreamOfInt16.java | 2 +- .../core/geometry/AttributeStreamOfInt32.java | 2 +- .../core/geometry/AttributeStreamOfInt64.java | 2 +- .../core/geometry/AttributeStreamOfInt8.java | 2 +- .../java/com/esri/core/geometry/Boundary.java | 2 +- .../com/esri/core/geometry/BucketSort.java | 2 +- .../java/com/esri/core/geometry/Bufferer.java | 2 +- .../esri/core/geometry/ByteBufferCursor.java | 2 +- .../com/esri/core/geometry/ClassicSort.java | 2 +- .../java/com/esri/core/geometry/Clipper.java | 2 +- .../com/esri/core/geometry/Clusterer.java | 2 +- .../esri/core/geometry/ConstructOffset.java | 2 +- .../com/esri/core/geometry/ConvexHull.java | 2 +- .../esri/core/geometry/CrackAndCluster.java | 2 +- .../java/com/esri/core/geometry/Cracker.java | 2 +- .../java/com/esri/core/geometry/Cutter.java | 2 +- .../com/esri/core/geometry/DirtyFlags.java | 2 +- .../com/esri/core/geometry/ECoordinate.java | 2 +- .../com/esri/core/geometry/EditShape.java | 2 +- .../java/com/esri/core/geometry/Envelope.java | 2 +- .../com/esri/core/geometry/Envelope1D.java | 2 +- .../com/esri/core/geometry/Envelope2D.java | 2 +- .../geometry/Envelope2DIntersectorImpl.java | 2 +- .../com/esri/core/geometry/Envelope3D.java | 2 +- .../java/com/esri/core/geometry/GeoDist.java | 2 +- .../core/geometry/GeoJsonExportFlags.java | 2 +- .../core/geometry/GeoJsonImportFlags.java | 2 +- .../esri/core/geometry/GeodeticCurveType.java | 2 +- .../java/com/esri/core/geometry/Geometry.java | 2 +- .../core/geometry/GeometryAccelerators.java | 2 +- .../esri/core/geometry/GeometryCursor.java | 2 +- .../core/geometry/GeometryCursorAppend.java | 2 +- .../esri/core/geometry/GeometryEngine.java | 2 +- .../esri/core/geometry/GeometryException.java | 2 +- .../core/geometry/GeometrySerializer.java | 2 +- .../esri/core/geometry/IndexHashTable.java | 2 +- .../esri/core/geometry/IndexMultiDCList.java | 2 +- .../esri/core/geometry/IndexMultiList.java | 2 +- .../com/esri/core/geometry/InternalUtils.java | 2 +- .../java/com/esri/core/geometry/Interop.java | 2 +- .../esri/core/geometry/IntervalTreeImpl.java | 2 +- .../core/geometry/JSONArrayEnumerator.java | 2 +- .../core/geometry/JSONObjectEnumerator.java | 2 +- .../com/esri/core/geometry/JSONUtils.java | 2 +- .../com/esri/core/geometry/JsonCursor.java | 2 +- .../esri/core/geometry/JsonParserCursor.java | 2 +- .../esri/core/geometry/JsonParserReader.java | 2 +- .../com/esri/core/geometry/JsonReader.java | 2 +- .../esri/core/geometry/JsonStringWriter.java | 2 +- .../esri/core/geometry/JsonValueReader.java | 2 +- .../com/esri/core/geometry/JsonWriter.java | 2 +- .../java/com/esri/core/geometry/Line.java | 2 +- .../geometry/ListeningGeometryCursor.java | 2 +- .../com/esri/core/geometry/MapGeometry.java | 2 +- .../esri/core/geometry/MapGeometryCursor.java | 2 +- .../esri/core/geometry/MapOGCStructure.java | 2 +- .../com/esri/core/geometry/MathUtils.java | 2 +- .../core/geometry/MgrsConversionMode.java | 2 +- .../com/esri/core/geometry/MultiPath.java | 2 +- .../com/esri/core/geometry/MultiPathImpl.java | 2 +- .../com/esri/core/geometry/MultiPoint.java | 2 +- .../esri/core/geometry/MultiPointImpl.java | 2 +- .../core/geometry/MultiVertexGeometry.java | 2 +- .../geometry/MultiVertexGeometryImpl.java | 2 +- .../esri/core/geometry/NonSimpleResult.java | 2 +- .../com/esri/core/geometry/NumberUtils.java | 2 +- .../com/esri/core/geometry/OGCStructure.java | 2 +- .../esri/core/geometry/ObjectCacheTable.java | 2 +- .../java/com/esri/core/geometry/Operator.java | 2 +- .../esri/core/geometry/OperatorBoundary.java | 2 +- .../core/geometry/OperatorBoundaryLocal.java | 2 +- .../geometry/OperatorBoundaryLocalCursor.java | 2 +- .../esri/core/geometry/OperatorBuffer.java | 2 +- .../core/geometry/OperatorBufferCursor.java | 2 +- .../core/geometry/OperatorBufferLocal.java | 2 +- .../com/esri/core/geometry/OperatorClip.java | 2 +- .../core/geometry/OperatorClipCursor.java | 2 +- .../esri/core/geometry/OperatorClipLocal.java | 2 +- .../esri/core/geometry/OperatorContains.java | 2 +- .../core/geometry/OperatorContainsLocal.java | 2 +- .../core/geometry/OperatorConvexHull.java | 2 +- .../geometry/OperatorConvexHullCursor.java | 2 +- .../geometry/OperatorConvexHullLocal.java | 2 +- .../esri/core/geometry/OperatorCrosses.java | 2 +- .../core/geometry/OperatorCrossesLocal.java | 2 +- .../com/esri/core/geometry/OperatorCut.java | 2 +- .../esri/core/geometry/OperatorCutCursor.java | 2 +- .../esri/core/geometry/OperatorCutLocal.java | 2 +- .../geometry/OperatorDensifyByLength.java | 2 +- .../OperatorDensifyByLengthCursor.java | 2 +- .../OperatorDensifyByLengthLocal.java | 2 +- .../core/geometry/OperatorDifference.java | 2 +- .../geometry/OperatorDifferenceCursor.java | 2 +- .../geometry/OperatorDifferenceLocal.java | 2 +- .../esri/core/geometry/OperatorDisjoint.java | 2 +- .../core/geometry/OperatorDisjointLocal.java | 2 +- .../esri/core/geometry/OperatorDistance.java | 2 +- .../core/geometry/OperatorDistanceLocal.java | 2 +- .../esri/core/geometry/OperatorEquals.java | 2 +- .../core/geometry/OperatorEqualsLocal.java | 2 +- .../geometry/OperatorExportToESRIShape.java | 2 +- .../OperatorExportToESRIShapeCursor.java | 2 +- .../OperatorExportToESRIShapeLocal.java | 2 +- .../core/geometry/OperatorExportToJson.java | 2 +- .../geometry/OperatorExportToJsonCursor.java | 2 +- .../geometry/OperatorExportToJsonLocal.java | 2 +- .../core/geometry/OperatorExportToWkb.java | 2 +- .../geometry/OperatorExportToWkbLocal.java | 2 +- .../core/geometry/OperatorExportToWkt.java | 2 +- .../geometry/OperatorExportToWktLocal.java | 2 +- .../esri/core/geometry/OperatorFactory.java | 2 +- .../core/geometry/OperatorFactoryLocal.java | 2 +- .../core/geometry/OperatorGeneralize.java | 2 +- .../geometry/OperatorGeneralizeCursor.java | 2 +- .../geometry/OperatorGeneralizeLocal.java | 2 +- .../core/geometry/OperatorGeodesicBuffer.java | 2 +- .../geometry/OperatorGeodesicBufferLocal.java | 2 +- .../core/geometry/OperatorGeodeticArea.java | 2 +- .../geometry/OperatorGeodeticAreaLocal.java | 2 +- .../OperatorGeodeticDensifyByLength.java | 2 +- .../OperatorGeodeticDensifyLocal.java | 2 +- .../core/geometry/OperatorGeodeticLength.java | 2 +- .../geometry/OperatorGeodeticLengthLocal.java | 2 +- .../geometry/OperatorImportFromESRIShape.java | 2 +- .../OperatorImportFromESRIShapeCursor.java | 2 +- .../OperatorImportFromESRIShapeLocal.java | 2 +- .../geometry/OperatorImportFromGeoJson.java | 2 +- .../OperatorImportFromGeoJsonLocal.java | 2 +- .../core/geometry/OperatorImportFromJson.java | 2 +- .../OperatorImportFromJsonCursor.java | 2 +- .../geometry/OperatorImportFromJsonLocal.java | 2 +- .../core/geometry/OperatorImportFromWkb.java | 2 +- .../geometry/OperatorImportFromWkbLocal.java | 2 +- .../core/geometry/OperatorImportFromWkt.java | 2 +- .../geometry/OperatorImportFromWktLocal.java | 2 +- .../OperatorInternalRelationUtils.java | 2 +- .../core/geometry/OperatorIntersection.java | 2 +- .../geometry/OperatorIntersectionCursor.java | 2 +- .../geometry/OperatorIntersectionLocal.java | 2 +- .../core/geometry/OperatorIntersects.java | 2 +- .../geometry/OperatorIntersectsLocal.java | 2 +- .../esri/core/geometry/OperatorOffset.java | 2 +- .../core/geometry/OperatorOffsetCursor.java | 2 +- .../core/geometry/OperatorOffsetLocal.java | 2 +- .../esri/core/geometry/OperatorOverlaps.java | 2 +- .../core/geometry/OperatorOverlapsLocal.java | 2 +- .../esri/core/geometry/OperatorProject.java | 2 +- .../core/geometry/OperatorProjectLocal.java | 2 +- .../core/geometry/OperatorProximity2D.java | 2 +- .../geometry/OperatorProximity2DLocal.java | 2 +- .../esri/core/geometry/OperatorRelate.java | 2 +- .../core/geometry/OperatorRelateLocal.java | 2 +- .../OperatorShapePreservingDensify.java | 2 +- .../OperatorShapePreservingDensifyLocal.java | 2 +- .../core/geometry/OperatorSimpleRelation.java | 2 +- .../esri/core/geometry/OperatorSimplify.java | 2 +- .../core/geometry/OperatorSimplifyCursor.java | 2 +- .../geometry/OperatorSimplifyCursorOGC.java | 2 +- .../core/geometry/OperatorSimplifyLocal.java | 2 +- .../geometry/OperatorSimplifyLocalHelper.java | 2 +- .../geometry/OperatorSimplifyLocalOGC.java | 2 +- .../core/geometry/OperatorSimplifyOGC.java | 2 +- .../geometry/OperatorSymmetricDifference.java | 2 +- .../OperatorSymmetricDifferenceCursor.java | 2 +- .../OperatorSymmetricDifferenceLocal.java | 2 +- .../esri/core/geometry/OperatorTouches.java | 2 +- .../core/geometry/OperatorTouchesLocal.java | 2 +- .../com/esri/core/geometry/OperatorUnion.java | 2 +- .../core/geometry/OperatorUnionCursor.java | 39 +- .../core/geometry/OperatorUnionLocal.java | 2 +- .../esri/core/geometry/OperatorWithin.java | 2 +- .../core/geometry/OperatorWithinLocal.java | 2 +- .../geometry/PairwiseIntersectorImpl.java | 2 +- .../com/esri/core/geometry/PathFlags.java | 2 +- .../java/com/esri/core/geometry/PeDouble.java | 2 +- .../geometry/PlaneSweepCrackerHelper.java | 2 +- .../java/com/esri/core/geometry/Point.java | 2 +- .../java/com/esri/core/geometry/Point2D.java | 2 +- .../java/com/esri/core/geometry/Point3D.java | 2 +- .../core/geometry/PointInPolygonHelper.java | 2 +- .../java/com/esri/core/geometry/Polygon.java | 4 +- .../com/esri/core/geometry/PolygonUtils.java | 2 +- .../java/com/esri/core/geometry/Polyline.java | 2 +- .../com/esri/core/geometry/PolylinePath.java | 2 +- .../esri/core/geometry/ProgressTracker.java | 2 +- .../geometry/ProjectionTransformation.java | 2 +- .../esri/core/geometry/Proximity2DResult.java | 2 +- .../geometry/Proximity2DResultComparator.java | 2 +- .../java/com/esri/core/geometry/QuadTree.java | 2 +- .../com/esri/core/geometry/QuadTreeImpl.java | 2 +- .../core/geometry/RasterizedGeometry2D.java | 2 +- .../geometry/RasterizedGeometry2DImpl.java | 62 ++-- .../core/geometry/RelationalOperations.java | 2 +- .../geometry/RelationalOperationsMatrix.java | 2 +- .../core/geometry/RingOrientationFixer.java | 2 +- .../java/com/esri/core/geometry/Segment.java | 2 +- .../com/esri/core/geometry/SegmentBuffer.java | 2 +- .../com/esri/core/geometry/SegmentFlags.java | 2 +- .../core/geometry/SegmentIntersector.java | 2 +- .../esri/core/geometry/SegmentIterator.java | 2 +- .../core/geometry/SegmentIteratorImpl.java | 2 +- .../esri/core/geometry/ShapeExportFlags.java | 2 +- .../esri/core/geometry/ShapeImportFlags.java | 2 +- .../esri/core/geometry/ShapeModifiers.java | 2 +- .../com/esri/core/geometry/ShapeType.java | 2 +- .../core/geometry/SimpleByteBufferCursor.java | 2 +- .../core/geometry/SimpleGeometryCursor.java | 2 +- .../esri/core/geometry/SimpleJsonCursor.java | 2 +- .../core/geometry/SimpleJsonParserCursor.java | 2 +- .../geometry/SimpleMapGeometryCursor.java | 2 +- .../esri/core/geometry/SimpleRasterizer.java | 335 ++++++++++++------ .../com/esri/core/geometry/Simplificator.java | 2 +- .../esri/core/geometry/SpatialReference.java | 2 +- .../core/geometry/SpatialReferenceImpl.java | 2 +- .../geometry/SpatialReferenceSerializer.java | 2 +- .../geometry/StridedIndexTypeCollection.java | 2 +- .../com/esri/core/geometry/StringUtils.java | 2 +- .../esri/core/geometry/SweepComparator.java | 2 +- .../core/geometry/SweepMonkierComparator.java | 2 +- .../com/esri/core/geometry/TopoGraph.java | 2 +- .../core/geometry/TopologicalOperations.java | 2 +- .../esri/core/geometry/Transformation2D.java | 2 +- .../esri/core/geometry/Transformation3D.java | 2 +- .../java/com/esri/core/geometry/Treap.java | 2 +- .../core/geometry/UserCancelException.java | 2 +- .../esri/core/geometry/VertexDescription.java | 2 +- .../VertexDescriptionDesignerImpl.java | 2 +- .../core/geometry/VertexDescriptionHash.java | 2 +- .../com/esri/core/geometry/WkbByteOrder.java | 2 +- .../esri/core/geometry/WkbExportFlags.java | 2 +- .../esri/core/geometry/WkbGeometryType.java | 2 +- .../esri/core/geometry/WkbImportFlags.java | 2 +- .../java/com/esri/core/geometry/Wkid.java | 2 +- src/main/java/com/esri/core/geometry/Wkt.java | 2 +- .../esri/core/geometry/WktExportFlags.java | 2 +- .../esri/core/geometry/WktImportFlags.java | 2 +- .../com/esri/core/geometry/WktParser.java | 2 +- .../com/esri/core/geometry/TestPolygon.java | 1 - .../geometry/TestRasterizedGeometry2D.java | 19 + 242 files changed, 527 insertions(+), 409 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java index 42c71a58..54f03ba2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 2009eaf0..18ae0fc1 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -405,14 +405,14 @@ public void insertRange(int start, AttributeStreamBase src, int srcStart, if (!bForward && (stride < 1 || count % stride != 0)) throw new IllegalArgumentException(); - int excess_space = m_size - validSize; + int excess_space = m_size - validSize; if (excess_space < count) { int original_size = m_size; resize(original_size + count - excess_space); } - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - start); if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 3577267b..1e90b35b 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index 5e9cf181..d4af7efe 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 609a19fd..8aa48e56 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 3498770b..62516ea2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index 2d10396a..b0a746e4 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java index 13958cb7..7ce5d361 100644 --- a/src/main/java/com/esri/core/geometry/Boundary.java +++ b/src/main/java/com/esri/core/geometry/Boundary.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/BucketSort.java b/src/main/java/com/esri/core/geometry/BucketSort.java index abd698c7..5039a4e8 100644 --- a/src/main/java/com/esri/core/geometry/BucketSort.java +++ b/src/main/java/com/esri/core/geometry/BucketSort.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index 587287fd..ebe961ce 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ByteBufferCursor.java b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java index 53f0121a..15b65905 100644 --- a/src/main/java/com/esri/core/geometry/ByteBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ClassicSort.java b/src/main/java/com/esri/core/geometry/ClassicSort.java index 57691017..edf1c9d1 100644 --- a/src/main/java/com/esri/core/geometry/ClassicSort.java +++ b/src/main/java/com/esri/core/geometry/ClassicSort.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java index 2af2e0a2..ca0fad19 100644 --- a/src/main/java/com/esri/core/geometry/Clipper.java +++ b/src/main/java/com/esri/core/geometry/Clipper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java index 8ff5efd8..528ea914 100644 --- a/src/main/java/com/esri/core/geometry/Clusterer.java +++ b/src/main/java/com/esri/core/geometry/Clusterer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java index 89db1b0b..3b143a00 100644 --- a/src/main/java/com/esri/core/geometry/ConstructOffset.java +++ b/src/main/java/com/esri/core/geometry/ConstructOffset.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index 7c5a4e5a..bafe51e0 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index 6f6d8247..89d40da9 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index cd8b9a72..88b4f6b8 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index d575e1de..f56dc5de 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java index 030539fe..36ccafda 100644 --- a/src/main/java/com/esri/core/geometry/DirtyFlags.java +++ b/src/main/java/com/esri/core/geometry/DirtyFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java index cfc3146b..253a05b6 100644 --- a/src/main/java/com/esri/core/geometry/ECoordinate.java +++ b/src/main/java/com/esri/core/geometry/ECoordinate.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index a85ca5a2..36ce09a8 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 3f3653f1..6991f26e 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 993eef36..9e30e08b 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index f2e4ef41..3b9a02f7 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index d9430f96..98d7ad8c 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 1dd38ef6..c1960fd6 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 9a1b410d..81225134 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java index 04da23ce..027bf2ac 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java index e2539151..a464d20b 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java index b07a73e1..3dc053af 100644 --- a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java +++ b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 6e16c0e6..e5d08afa 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 138b134e..2ccc84cc 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryCursor.java b/src/main/java/com/esri/core/geometry/GeometryCursor.java index 503dfe4c..3c2ef5d6 100644 --- a/src/main/java/com/esri/core/geometry/GeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/GeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java index cbf3faee..dec0b708 100644 --- a/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java +++ b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 048994dd..e027dd6c 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 41ea2984..97638685 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java index 8bf9f401..b91c16f7 100644 --- a/src/main/java/com/esri/core/geometry/GeometrySerializer.java +++ b/src/main/java/com/esri/core/geometry/GeometrySerializer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java index 4e60b59e..b0117bb8 100644 --- a/src/main/java/com/esri/core/geometry/IndexHashTable.java +++ b/src/main/java/com/esri/core/geometry/IndexHashTable.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IndexMultiDCList.java b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java index b7a0a119..c20adbec 100644 --- a/src/main/java/com/esri/core/geometry/IndexMultiDCList.java +++ b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IndexMultiList.java b/src/main/java/com/esri/core/geometry/IndexMultiList.java index 44b1da78..b008094a 100644 --- a/src/main/java/com/esri/core/geometry/IndexMultiList.java +++ b/src/main/java/com/esri/core/geometry/IndexMultiList.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index 42bd8362..257eb63f 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Interop.java b/src/main/java/com/esri/core/geometry/Interop.java index 38df1b52..b47801c7 100644 --- a/src/main/java/com/esri/core/geometry/Interop.java +++ b/src/main/java/com/esri/core/geometry/Interop.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index 1237e798..acdc10d8 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java index 5b2d93d7..1350f623 100644 --- a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java index f474a16c..7b8f2e8b 100644 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java index 1ec51185..71feb32a 100644 --- a/src/main/java/com/esri/core/geometry/JSONUtils.java +++ b/src/main/java/com/esri/core/geometry/JSONUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonCursor.java b/src/main/java/com/esri/core/geometry/JsonCursor.java index 30e1beed..0af0024f 100644 --- a/src/main/java/com/esri/core/geometry/JsonCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonParserCursor.java b/src/main/java/com/esri/core/geometry/JsonParserCursor.java index fe3605ab..1a1f14f3 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonParserCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index efa6f55d..80666579 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index 1b3a4f03..ba3b8cca 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index 6947f5a1..f324a197 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java index 9f320029..d7c4b639 100644 --- a/src/main/java/com/esri/core/geometry/JsonValueReader.java +++ b/src/main/java/com/esri/core/geometry/JsonValueReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 9e71beb8..0baa0f2b 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 95126e81..a3c5e570 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java index 04de301e..965a6798 100644 --- a/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index fc1ef12b..d7161d52 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MapGeometryCursor.java b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java index 81b0b195..b7cec8e4 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MapOGCStructure.java b/src/main/java/com/esri/core/geometry/MapOGCStructure.java index 61ec600e..c4eb5241 100644 --- a/src/main/java/com/esri/core/geometry/MapOGCStructure.java +++ b/src/main/java/com/esri/core/geometry/MapOGCStructure.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index a338dccd..208fe4ba 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java index 21b54da8..b92ef646 100644 --- a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java +++ b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 1fb76d68..f2b80e89 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index ba566436..43ed98a5 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 3604e108..2d2cbf28 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index b29e35c3..b3d0a4b7 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java index 51948f74..668d6b33 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 9fbe1756..11ef7ed4 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java index ac79c687..87d95111 100644 --- a/src/main/java/com/esri/core/geometry/NonSimpleResult.java +++ b/src/main/java/com/esri/core/geometry/NonSimpleResult.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 7de7558d..209df086 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index 14dde139..9f0875b9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ObjectCacheTable.java b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java index b655ed70..54b18abd 100644 --- a/src/main/java/com/esri/core/geometry/ObjectCacheTable.java +++ b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 41f69129..ffceb73f 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundary.java b/src/main/java/com/esri/core/geometry/OperatorBoundary.java index fd34bea1..e2a8fc1c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundary.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundary.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java index 585ded53..0188affd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java index fa01fe80..c06c3d53 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java index 2b8f02b4..cca44f54 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorBuffer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java index a779db3a..397ec89c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java index 86f7e6bb..5e195d57 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorClip.java b/src/main/java/com/esri/core/geometry/OperatorClip.java index 2dca9085..8b1907af 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClip.java +++ b/src/main/java/com/esri/core/geometry/OperatorClip.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorClipCursor.java b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java index 04c8ef7e..f45484fa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClipCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorClipLocal.java b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java index 50aa6216..9f201322 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClipLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorContains.java b/src/main/java/com/esri/core/geometry/OperatorContains.java index ccc21d6a..bbc4cf27 100644 --- a/src/main/java/com/esri/core/geometry/OperatorContains.java +++ b/src/main/java/com/esri/core/geometry/OperatorContains.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java index d9ea5d24..3bc67286 100644 --- a/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHull.java b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java index bd9ea418..a00b5a77 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHull.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java index 8c304a62..11b32738 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java index 155313a7..e314d34e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCrosses.java b/src/main/java/com/esri/core/geometry/OperatorCrosses.java index 3a354bc5..0780879a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCrosses.java +++ b/src/main/java/com/esri/core/geometry/OperatorCrosses.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java index d5b55abb..2c167080 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCut.java b/src/main/java/com/esri/core/geometry/OperatorCut.java index 59c5b589..762db7d6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCut.java +++ b/src/main/java/com/esri/core/geometry/OperatorCut.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCutCursor.java b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java index 266bdd99..cd5dae6c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCutCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCutLocal.java b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java index 98613301..75dccf2c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCutLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java index c01ec80d..085dd397 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java index b634ff59..3a7267aa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java index dbffbf4b..f6eb5e0a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 11c1295a..5e4b40e6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java index 021d1512..8c2419af 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index 2121729b..02006bfc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDisjoint.java b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java index 9892661b..9404759f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDisjoint.java +++ b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java index e65c0b3e..5bbaa1a2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDistance.java b/src/main/java/com/esri/core/geometry/OperatorDistance.java index 206bbf93..d29c2c43 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDistance.java +++ b/src/main/java/com/esri/core/geometry/OperatorDistance.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java index 76ac6a35..9cb35c69 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorEquals.java b/src/main/java/com/esri/core/geometry/OperatorEquals.java index 7fb1d5b7..2fb6d3ba 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEquals.java +++ b/src/main/java/com/esri/core/geometry/OperatorEquals.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java index d265c55a..d5edf302 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java index 840a1707..56aa754b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java index 13d821ed..67d4dfce 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java index 9704054e..5b21e632 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java index 9b548d4f..32bfc078 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java index 85547549..b14fbb3e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java index 5c5f819d..65554574 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java index d4418b2c..fba948cb 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index 929e9168..b87b6a8f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java index 6e893b14..10eab9b2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index 5fe4398f..0b3a4466 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorFactory.java b/src/main/java/com/esri/core/geometry/OperatorFactory.java index 680cdfee..66e6030a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactory.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactory.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index a77628d3..f07cea96 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralize.java b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java index af6223bd..ce8c4703 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralize.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index eb860616..2b1d5ba1 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java index cf759c4b..c46e0f8c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java index fd0c9bdf..4e7068f7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java index b3475eb9..7b9f97ba 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java index 3e45ed36..ee2e0df7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java index 441f0ec0..12a3f805 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java index d9a660e6..5efcb134 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java index 011bc4b2..f704c395 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java index 47f79482..268dbc5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java index e878027d..306e3c74 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java index be7bf6cc..75bc1375 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java index 982abe03..2186b4e4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java index 4bcdd3a0..16c2013f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 9032c913..2c0c0287 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index b5d0c788..8ed6b64e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java index c05438eb..6a88d05c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index cc56169b..56f79a87 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java index f92842fb..4e41a491 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java index 9550e4cf..a80e3bd9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java index 6fa9c01a..1fa9685c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java index b64c70ac..60b0460d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java index 3a9bb232..3b6a74f0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java index 7f78b59d..5c61ad20 100644 --- a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java +++ b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index c1230446..2f23f11c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index 7706d83e..edcf0313 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java index bb13dcf3..71d2d9e6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersects.java b/src/main/java/com/esri/core/geometry/OperatorIntersects.java index d7984fa8..ace6c277 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersects.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersects.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java index 5a492095..1abefd6a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java index 910ccd8a..7b4f3673 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffset.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffset.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java index 5f27c667..76f1f982 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java index 7006b42d..06c7f3c6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOverlaps.java b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java index d1a362b4..c7b3b207 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOverlaps.java +++ b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java index f767cafd..f9114687 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java index da2a1712..db74ae12 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProject.java +++ b/src/main/java/com/esri/core/geometry/OperatorProject.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java index 78598ca6..ae753433 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java index 80327e18..7449fc95 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java index 363c29a4..d62f514d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java index b1e435bf..d5542990 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelate.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelate.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java index c83fbee1..88af8ad0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java index 3aa5ed8e..3a4a31ce 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java index 01bcf3a7..68fd0a5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java index f4afeff7..1a08cf6d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplify.java b/src/main/java/com/esri/core/geometry/OperatorSimplify.java index 0e14b2f9..3e9beca9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplify.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplify.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java index 4be821a8..19c920df 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java index 277724d6..15e726af 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java index 6cb16dc1..4edc081e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index 62b35c26..b0bd4dd8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java index aa9c5a9f..6c725abb 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java index d433f0c7..c04ba0c4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index bb914e77..ea84ce0c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java index 92450ce3..3d265593 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java index e5de43db..e505a8dd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorTouches.java b/src/main/java/com/esri/core/geometry/OperatorTouches.java index a98de285..6ae67f38 100644 --- a/src/main/java/com/esri/core/geometry/OperatorTouches.java +++ b/src/main/java/com/esri/core/geometry/OperatorTouches.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java index f18f3541..48d32d89 100644 --- a/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index 69340bd2..8a0a8601 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java index 53164ef2..0ddd88b8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -183,26 +183,23 @@ private boolean step_(){ } if (m_added_geoms > 0) { - for (int dim = 0; dim <= m_max_dimension; dim++) - { - while (m_dim_geom_counts[dim] > 1) - { - ArrayList batch_to_union = collect_geometries_to_union(dim); - boolean serial_execution = true; - if (serial_execution) - { - if (batch_to_union.size() != 0) - { - Geometry geomRes = TopologicalOperations.dissolveDirty(batch_to_union, m_spatial_reference, m_progress_tracker); - add_geom(dim, true, geomRes); - } - else - { - break; - } - } - } - } + for (int dim = 0; dim <= m_max_dimension; dim++) { + while (m_dim_geom_counts[dim] > 1) { + ArrayList batch_to_union = collect_geometries_to_union(dim); + boolean serial_execution = true; + if (serial_execution) { + if (batch_to_union.size() != 0) { + Geometry geomRes = TopologicalOperations + .dissolveDirty(batch_to_union, + m_spatial_reference, + m_progress_tracker); + add_geom(dim, true, geomRes); + } else { + break; + } + } + } + } } return m_b_done; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java index e3cf5ea7..1b723282 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorWithin.java b/src/main/java/com/esri/core/geometry/OperatorWithin.java index 9032718f..00a0c15f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorWithin.java +++ b/src/main/java/com/esri/core/geometry/OperatorWithin.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java index 9958ff90..a370a8fe 100644 --- a/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java index 465c78cd..cb3c5c00 100644 --- a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PathFlags.java b/src/main/java/com/esri/core/geometry/PathFlags.java index b163559e..1fa09c35 100644 --- a/src/main/java/com/esri/core/geometry/PathFlags.java +++ b/src/main/java/com/esri/core/geometry/PathFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PeDouble.java b/src/main/java/com/esri/core/geometry/PeDouble.java index 90bf9abf..fd33af49 100644 --- a/src/main/java/com/esri/core/geometry/PeDouble.java +++ b/src/main/java/com/esri/core/geometry/PeDouble.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 41db31be..4bdf00cc 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 9bbf20a4..0f553257 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 5525626b..f9fa71ab 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index 5f509019..a8316ff3 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java index 8664ed8d..801b2b81 100644 --- a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 0c07f6f4..6d2a97f4 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -158,6 +158,7 @@ public interface FillRule { *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. *Can be use by drawing code to pass around the fill rule of graphic path. *This property is not persisted in any format yet. + *See also Polygon.FillRule. */ public void setFillRule(int rule) { m_impl.setFillRule(rule); @@ -168,6 +169,7 @@ public void setFillRule(int rule) { *Changing the fill rule on the polygon that has no self intersections has no physical effect. *Can be use by drawing code to pass around the fill rule of graphic path. *This property is not persisted in any format yet. + *See also Polygon.FillRule. */ public int getFillRule() { return m_impl.getFillRule(); diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index 9a6b52f1..26701d30 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 1b648f6c..b95e9f81 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PolylinePath.java b/src/main/java/com/esri/core/geometry/PolylinePath.java index b3c06dc8..6d3342da 100644 --- a/src/main/java/com/esri/core/geometry/PolylinePath.java +++ b/src/main/java/com/esri/core/geometry/PolylinePath.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java index d4153375..09933688 100644 --- a/src/main/java/com/esri/core/geometry/ProgressTracker.java +++ b/src/main/java/com/esri/core/geometry/ProgressTracker.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ProjectionTransformation.java b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java index 4f2aedde..858bc03c 100644 --- a/src/main/java/com/esri/core/geometry/ProjectionTransformation.java +++ b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java index 04ac528d..0e1efe71 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResult.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResult.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java index 1be0cfbb..0404aa80 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index 59f976d5..9f6be163 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 3b9afd77..cbef824b 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 94f187d9..46ccd5c6 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index 16e3dd1c..556a9fe0 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -67,22 +67,26 @@ public ScanCallbackImpl(int[] bitmap, int scanlineWidth) { } public void setColor(SimpleRasterizer rasterizer, int color) { + if (m_color != color) + rasterizer.flush(); + m_color = color;// set new color } @Override - public void drawScan(int y, int x, int numPxls) { - int x0 = x; - int x1 = x + numPxls; - if (x1 > m_width) - x1 = m_width; - - int scanlineStart = y * m_scanlineWidth; - for (int xx = x0; xx < x1; xx++) { - m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 - // bit - // per - // color + public void drawScan(int[] scans, int scanCount3) { + for (int i = 0; i < scanCount3; ) { + int x0 = scans[i++]; + int x1 = scans[i++]; + int y = scans[i++]; + + int scanlineStart = y * m_scanlineWidth; + for (int xx = x0; xx < x1; xx++) { + m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 + // bit + // per + // color + } } } } @@ -122,33 +126,7 @@ void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { } void fillEnvelope(SimpleRasterizer rasterizer, Envelope2D envIn) { - /*if (!m_identity) { - Point2D fan[] = new Point2D[4]; - envIn.queryCorners(fan); - fillConvexPolygon(rasterizer, fan, 4); - return; - }*/ - - Envelope2D env = new Envelope2D(0, 0, m_width, m_width); - if (!env.intersect(envIn)) - return; - - int x0 = (int) Math.round(env.xmin); - int x = (int) Math.round(env.xmax); - - int xn = NumberUtils.snap(x0, 0, m_width); - int xm = NumberUtils.snap(x, 0, m_width); - if (x0 < m_width && xn < xm) { - int y0 = (int) Math.round(env.ymin); - int y1 = (int) Math.round(env.ymax); - y0 = NumberUtils.snap(y0, 0, m_width); - y1 = NumberUtils.snap(y1, 0, m_width); - if (y0 < m_width) { - for (int y = y0; y < y1; y++) { - m_rasterizer.callback_.drawScan(y, xn, xm - xn); - } - } - } + rasterizer.fillEnvelope(envIn); } void strokeDrawPolyPath(SimpleRasterizer rasterizer, @@ -253,8 +231,7 @@ static RasterizedGeometry2DImpl createImpl(Geometry geom, double toleranceXY, int rasterSizeBytes) { RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, toleranceXY, rasterSizeBytes); - rgImpl.init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, - rasterSizeBytes); + return rgImpl; } @@ -267,7 +244,6 @@ static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, double toleranceXY, int rasterSizeBytes) { RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, toleranceXY, rasterSizeBytes); - rgImpl.init(geom, toleranceXY, rasterSizeBytes); return rgImpl; } @@ -360,6 +336,7 @@ void init(MultiVertexGeometryImpl geom, double toleranceXY, m_x0 = m_transform.xd; m_y0 = m_transform.yd; buildLevels(); + //dbgSaveToBitmap("c:/temp/_dbg.bmp"); } boolean tryRenderAsSmallEnvelope_(Envelope2D env) { @@ -389,6 +366,7 @@ boolean tryRenderAsSmallEnvelope_(Envelope2D env) { } void buildLevels() { + m_rasterizer.flush(); int iStart = 0; int iStartNext = m_width * m_scanLineSize; int width = m_width; diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 34c84cf2..0b73561a 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 7660fc4e..1d93f5c8 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java index 970ebe3e..84fa66b5 100644 --- a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java +++ b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index b80a4236..be94b723 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java index 17df913d..a5d85076 100644 --- a/src/main/java/com/esri/core/geometry/SegmentBuffer.java +++ b/src/main/java/com/esri/core/geometry/SegmentBuffer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentFlags.java b/src/main/java/com/esri/core/geometry/SegmentFlags.java index ab3a37e9..7f0b5da1 100644 --- a/src/main/java/com/esri/core/geometry/SegmentFlags.java +++ b/src/main/java/com/esri/core/geometry/SegmentFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index ffbadb18..2bf63af3 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index b563ccdd..8312e028 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java index c5f97d19..03761997 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeExportFlags.java b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java index 03cc9b3f..ac04ba51 100644 --- a/src/main/java/com/esri/core/geometry/ShapeExportFlags.java +++ b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeImportFlags.java b/src/main/java/com/esri/core/geometry/ShapeImportFlags.java index 1b2742c9..876a983d 100644 --- a/src/main/java/com/esri/core/geometry/ShapeImportFlags.java +++ b/src/main/java/com/esri/core/geometry/ShapeImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeModifiers.java b/src/main/java/com/esri/core/geometry/ShapeModifiers.java index 2206c8e9..9ba3b702 100644 --- a/src/main/java/com/esri/core/geometry/ShapeModifiers.java +++ b/src/main/java/com/esri/core/geometry/ShapeModifiers.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeType.java b/src/main/java/com/esri/core/geometry/ShapeType.java index 17efe606..5d1fafa3 100644 --- a/src/main/java/com/esri/core/geometry/ShapeType.java +++ b/src/main/java/com/esri/core/geometry/ShapeType.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java b/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java index 5b884949..e7595439 100644 --- a/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java index 5ec4034d..3c185400 100644 --- a/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java index 188d7d4d..1af987a9 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java index 1d30235c..5f034a82 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java index 6d6879ed..fb281b76 100644 --- a/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 3ca4d304..27a87351 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -1,5 +1,5 @@ /* - Copyright 2013 Esri + Copyright 2013-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -38,6 +39,7 @@ public class SimpleRasterizer { * Even odd fill rule */ public final static int EVEN_ODD = 0; + /** * Winding fill rule */ @@ -46,11 +48,11 @@ public class SimpleRasterizer { public static interface ScanCallback { /** * Rasterizer calls this method for each scan it produced - * @param y the Y coordinate for the scan - * @param x the X coordinate for the scan - * @param numPxls the number of pixels in the scan + * @param scan array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), + * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. + * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ - public abstract void drawScan(int y, int x, int numPxls); + public abstract void drawScan(int[] scans, int scanCount3); } public SimpleRasterizer() { @@ -68,31 +70,44 @@ public void setup(int width, int height, ScanCallback callback) activeEdgesTable_ = null; numEdges_ = 0; callback_ = callback; + if (scanBuffer_ == null) + scanBuffer_ = new int[128 * 3]; + startAddingEdges(); } - public int getWidth() { + public final int getWidth() { return width_; } - public int getHeight() { + public final int getHeight() { return height_; } + /** + * Flushes any cached scans. + */ + public final void flush() { + if (scanPtr_ > 0) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + /** * Adds edges of a triangle. */ - public void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { + public final void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { addEdge(x1, y1, x2, y2); addEdge(x2, y2, x3, y3); addEdge(x1, y1, x3, y3); } - + /** * Adds edges of the ring to the rasterizer. * @param xy interleaved coordinates x1, y1, x2, y2,... */ - public void addRing(double xy[]) { + public final void addRing(double xy[]) { for (int i = 2; i < xy.length; i += 2) { addEdge(xy[i-2], xy[i - 1], xy[i], xy[i + 1]); } @@ -100,16 +115,33 @@ public void addRing(double xy[]) { /** * Call before starting the edges. + * * For example to render two polygons that consist of a single ring: * startAddingEdges(); * addRing(...); * renderEdges(Rasterizer.EVEN_ODD); * addRing(...); * renderEdges(Rasterizer.EVEN_ODD); + * + * For example to render a polygon consisting of three rings: + * startAddingEdges(); + * addRing(...); + * addRing(...); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); */ - public void startAddingEdges() { + public final void startAddingEdges() { if (numEdges_ > 0) { - ySortedEdges_ = null; + for (int i = 0; i < height_; i++) { + for (Edge e = ySortedEdges_[i]; e != null;) { + Edge p = e; + e = e.next; + p.next = null; + } + + ySortedEdges_[i] = null; + } + activeEdgesTable_ = null; } @@ -120,9 +152,13 @@ public void startAddingEdges() { /** * Renders all edges added so far, and removes them. - * @param fillMode + * Calls startAddingEdges after it's done. + * @param fillMode Fill mode for the polygon fill can be one of two values: EVEN_ODD or WINDING. + * + * Note, as any other graphics algorithm, the scan line rasterizer doesn't require polygons + * to be topologically simple, or have correct ring orientation. */ - public void renderEdges(int fillMode) { + public final void renderEdges(int fillMode) { evenOdd_ = fillMode == EVEN_ODD; for (int line = minY_; line <= maxY_; line++) { advanceAET_(); @@ -130,10 +166,6 @@ public void renderEdges(int fillMode) { emitScans_(); } - numEdges_ = 0; - if (activeEdgesTable_ != null) - activeEdgesTable_.clear(); - startAddingEdges();//reset for new edges } @@ -144,9 +176,10 @@ public void renderEdges(int fillMode) { * @param x2 * @param y2 */ - public void addEdge(double x1, double y1, double x2, double y2) { + public final void addEdge(double x1, double y1, double x2, double y2) { if (y1 == y2) return; + int dir = 1; if (y1 > y2) { double temp; @@ -180,27 +213,26 @@ else if (x1 >= width_ && x2 >= width_) y1 = 0; } - //We know that dxdy != 0, otherwise it would return earlier //do not clip x unless it is too small or too big int bigX = Math.max(width_ + 1, 0x7fffff); if (x1 < -0x7fffff) { - + //from earlier logic, x2 >= -1, therefore dxdy is not 0 y1 = (0 - x1) / dxdy + y1; x1 = 0; } else if (x1 > bigX) { - //we know that dx != 0, otherwise it would return earlier + //from earlier logic, x2 <= width_, therefore dxdy is not 0 y1 = (width_ - x1) / dxdy + y1; x1 = width_; } if (x2 < -0x7fffff) { - //we know that dx != 0, otherwise it would return earlier + //from earlier logic, x1 >= -1, therefore dxdy is not 0 y2 = (0 - x1) / dxdy + y1; x2 = 0; } else if (x2 > bigX) { - //we know that dx != 0, otherwise it would return earlier + //from earlier logic, x1 <= width_, therefore dxdy is not 0 y2 = (width_ - x1) / dxdy + y1; x2 = width_; } @@ -210,11 +242,7 @@ else if (x2 > bigX) { if (ystart == yend) return; - Edge e; - if (recycledEdges_ != null && recycledEdges_.size() > 0) - e = recycledEdges_.remove(recycledEdges_.size() - 1); - else - e = new Edge(); + Edge e = new Edge(); e.x = (long)(x1 * 4294967296.0); e.y = ystart; @@ -223,84 +251,155 @@ else if (x2 > bigX) { e.dir = dir; if (ySortedEdges_ == null) { - ySortedEdges_ = new ArrayList>(); - ySortedEdges_.ensureCapacity(height_); - for (int i = 0; i < height_; i++) { - ySortedEdges_.add(null); - } + ySortedEdges_ = new Edge[height_]; } - if (ySortedEdges_.get(e.y) == null) { - ySortedEdges_.set(e.y, new ArrayList()); - } + e.next = ySortedEdges_[e.y]; + ySortedEdges_[e.y] = e; - ySortedEdges_.get(e.y).add(e); if (e.y < minY_) minY_ = e.y; if (e.ymax > maxY_) maxY_ = e.ymax; + + numEdges_++; } - class Edge { + public final void fillEnvelope(Envelope2D envIn) { + Envelope2D env = new Envelope2D(0, 0, width_, height_); + if (!env.intersect(envIn)) + return; + + int x0 = (int)env.xmin; + int x = (int)env.xmax; + + int xn = NumberUtils.snap(x0, 0, width_); + int xm = NumberUtils.snap(x, 0, width_); + if (x0 < width_ && xn < xm) { + int y0 = (int)env.ymin; + int y1 = (int)env.ymax; + y0 = NumberUtils.snap(y0, 0, height_); + y1 = NumberUtils.snap(y1, 0, height_); + if (y0 < height_) { + for (int y = y0; y < y1; y++) { + scanBuffer_[scanPtr_++] = xn; + scanBuffer_[scanPtr_++] = xm; + scanBuffer_[scanPtr_++] = y; + if (scanPtr_ == scanBuffer_.length) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + } + } + } + + public final ScanCallback getScanCallback() { return callback_; } + + + //PRIVATE + + private static class Edge { long x; long dxdy; int y; int ymax; int dir; + Edge next; } - private void advanceAET_() { - if (activeEdgesTable_ != null && activeEdgesTable_.size() > 0) { - for (int i = 0, n = activeEdgesTable_.size(); i < n; i++) { - Edge e = activeEdgesTable_.get(i); - e.y++; - if (e.y == e.ymax) { - if (recycledEdges_ == null) { - recycledEdges_ = new ArrayList(); - } - - recycledEdges_.add(e); - activeEdgesTable_.set(i, null); - continue; - } + private final void advanceAET_() { + if (activeEdgesTable_ == null) + return; + + boolean needSort = false; + Edge prev = null; + for (Edge e = activeEdgesTable_; e != null; ) { + e.y++; + if (e.y == e.ymax) { + Edge p = e; e = e.next; + if (prev != null) + prev.next = e; + else + activeEdgesTable_ = e; - e.x += e.dxdy; + p.next = null; + continue; } + + e.x += e.dxdy; + if (prev != null && prev.x > e.x) + needSort = true; + + prev = e; + e = e.next; + } + + if (needSort) { + //resort to fix the order + activeEdgesTable_ = sortAET_(activeEdgesTable_); } } - private void addNewEdgesToAET_(int y) { - if (y >= ySortedEdges_.size()) + private final void addNewEdgesToAET_(int y) { + if (y >= height_) return; - - if (activeEdgesTable_ == null) - activeEdgesTable_ = new ArrayList(); - - ArrayList edgesOnLine = ySortedEdges_.get(y); + + Edge edgesOnLine = ySortedEdges_[y]; if (edgesOnLine != null) { - for (int i = 0, n = edgesOnLine.size(); i < n; i++) { - activeEdgesTable_.add(edgesOnLine.get(i)); + ySortedEdges_[y] = null; + edgesOnLine = sortAET_(edgesOnLine);//sort new edges + numEdges_ -= sortedNum_;//set in the sortAET + + // merge the edges with sorted AET - O(n) operation + Edge aet = activeEdgesTable_; + boolean first = true; + Edge newEdge = edgesOnLine; + Edge prev_aet = null; + while (aet != null && newEdge != null) { + if (aet.x > newEdge.x) { + if (first) + activeEdgesTable_ = newEdge; + + Edge p = newEdge.next; + newEdge.next = aet; + if (prev_aet != null) { + prev_aet.next = newEdge; + } + + prev_aet = newEdge; + newEdge = p; + } else { // aet.x <= newEdges.x + Edge p = aet.next; + aet.next = newEdge; + if (prev_aet != null) + prev_aet.next = aet; + + prev_aet = aet; + aet = p; + } + + first = false; } - edgesOnLine.clear(); + if (activeEdgesTable_ == null) + activeEdgesTable_ = edgesOnLine; } } - static int snap_(int x, int mi, int ma) { + private static int snap_(int x, int mi, int ma) { return x < mi ? mi : x > ma ? ma : x; } - private void emitScans_() { - sortAET_(); - - if (activeEdgesTable_ == null || activeEdgesTable_.size() == 0) + + private final void emitScans_() { + if (activeEdgesTable_ == null) return; int w = 0; - Edge e0 = activeEdgesTable_.get(0); + Edge e0 = activeEdgesTable_; int x0 = (int)(e0.x >> 32); - for (int i = 1; i < activeEdgesTable_.size(); i++) { - Edge e = activeEdgesTable_.get(i); + for (Edge e = e0.next; e != null; e = e.next) { if (evenOdd_) w ^= 1; else @@ -308,11 +407,17 @@ private void emitScans_() { if (e.x > e0.x) { int x = (int)(e.x >> 32); - if (w == 1) { + if (w != 0) { int xx0 = snap_(x0, 0, width_); int xx = snap_(x, 0, width_); if (xx > xx0 && xx0 < width_) { - callback_.drawScan(e.y, xx0, xx - xx0); + scanBuffer_[scanPtr_++] = xx0; + scanBuffer_[scanPtr_++] = xx; + scanBuffer_[scanPtr_++] = e.y; + if (scanPtr_ == scanBuffer_.length) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } } } @@ -322,56 +427,74 @@ private void emitScans_() { } } - static class EdgeComparator implements Comparator { + static private class EdgeComparator implements Comparator { @Override public int compare(Edge o1, Edge o2) { - if (o1 == null) - return o2 == null ? 0 : 1; - else if (o2 == null) - return -1; - + if (o1 == o2) + return 0; + return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0; } } - private static EdgeComparator edgeCompare_ = new EdgeComparator(); + private final static EdgeComparator edgeCompare_ = new EdgeComparator(); - private void sortAET_() { - if (!checkAETIsSorted_()) + private final Edge sortAET_(Edge aet) { + int num = 0; + for (Edge e = aet; e != null; e = e.next) + num++; + + sortedNum_ = num; + if (num == 1) + return aet; + + if (sortBuffer_ == null) + sortBuffer_ = new Edge[Math.max(num, 16)]; + + else if (sortBuffer_.length < num) + sortBuffer_ = new Edge[Math.max(num, sortBuffer_.length * 2)]; + { - Collections.sort(activeEdgesTable_, edgeCompare_); - while (activeEdgesTable_.size() > 0 && activeEdgesTable_.get(activeEdgesTable_.size() - 1) == null) - activeEdgesTable_.remove(activeEdgesTable_.size() - 1); + int i = 0; + for (Edge e = aet; e != null; e = e.next) + sortBuffer_[i++] = e; } - } - - private boolean checkAETIsSorted_() { - if (activeEdgesTable_ == null || activeEdgesTable_.size() == 0) - return true; - - Edge e0 = activeEdgesTable_.get(0); - if (e0 == null) - return false; - for (int i = 1; i < activeEdgesTable_.size(); i++) { - Edge e = activeEdgesTable_.get(i); - if (e == null || e.x < e0.x) { - return false; + if (num == 2) { + if (sortBuffer_[0].x > sortBuffer_[1].x) { + Edge tmp = sortBuffer_[0]; + sortBuffer_[0] = sortBuffer_[1]; + sortBuffer_[1] = tmp; } - e0 = e; + } + else { + Arrays.sort(sortBuffer_, 0, num, edgeCompare_); + } + + aet = sortBuffer_[0]; sortBuffer_[0] = null; + Edge prev = aet; + for (int i = 1; i < num; i++) { + prev.next = sortBuffer_[i]; + prev = sortBuffer_[i]; + sortBuffer_[i] = null; } - return true; + prev.next = null; + return aet; } - - private ArrayList recycledEdges_; - private ArrayList activeEdgesTable_; - private ArrayList> ySortedEdges_; - public ScanCallback callback_; + + private Edge activeEdgesTable_; + private Edge[] ySortedEdges_; + private Edge[] sortBuffer_; + private int[] scanBuffer_; + int scanPtr_; + private ScanCallback callback_; private int width_; private int height_; private int minY_; private int maxY_; private int numEdges_; + private int sortedNum_; private boolean evenOdd_; } + diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 8234b828..e0401cfa 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index a416c241..39b1e90a 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 081b2b64..772b8ee7 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java index 5df978f2..0cf25e6c 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 785e696f..c54cfe9a 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java index f859e9d8..3237e13c 100644 --- a/src/main/java/com/esri/core/geometry/StringUtils.java +++ b/src/main/java/com/esri/core/geometry/StringUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SweepComparator.java b/src/main/java/com/esri/core/geometry/SweepComparator.java index 9fb76798..a1f94376 100644 --- a/src/main/java/com/esri/core/geometry/SweepComparator.java +++ b/src/main/java/com/esri/core/geometry/SweepComparator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java index 6589ef64..2edeef22 100644 --- a/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java +++ b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index 1146860d..5f57251c 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index b6746704..def5cebe 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index 1d90b17c..f5d07a54 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 5f914381..02719a3c 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java index d654fa3d..89cd6383 100644 --- a/src/main/java/com/esri/core/geometry/Treap.java +++ b/src/main/java/com/esri/core/geometry/Treap.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/UserCancelException.java b/src/main/java/com/esri/core/geometry/UserCancelException.java index bf0b1f2b..d72479a1 100644 --- a/src/main/java/com/esri/core/geometry/UserCancelException.java +++ b/src/main/java/com/esri/core/geometry/UserCancelException.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java index 296e2af3..1fc1a6f6 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescription.java +++ b/src/main/java/com/esri/core/geometry/VertexDescription.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 9bec3a33..268b75bb 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java index c9f9e137..dfe372aa 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbByteOrder.java b/src/main/java/com/esri/core/geometry/WkbByteOrder.java index 9f474f20..c973d82e 100644 --- a/src/main/java/com/esri/core/geometry/WkbByteOrder.java +++ b/src/main/java/com/esri/core/geometry/WkbByteOrder.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbExportFlags.java b/src/main/java/com/esri/core/geometry/WkbExportFlags.java index d4637df8..115f39c7 100644 --- a/src/main/java/com/esri/core/geometry/WkbExportFlags.java +++ b/src/main/java/com/esri/core/geometry/WkbExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbGeometryType.java b/src/main/java/com/esri/core/geometry/WkbGeometryType.java index 07d142d0..20932cf9 100644 --- a/src/main/java/com/esri/core/geometry/WkbGeometryType.java +++ b/src/main/java/com/esri/core/geometry/WkbGeometryType.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbImportFlags.java b/src/main/java/com/esri/core/geometry/WkbImportFlags.java index 5f1f0392..fdadc8a5 100644 --- a/src/main/java/com/esri/core/geometry/WkbImportFlags.java +++ b/src/main/java/com/esri/core/geometry/WkbImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Wkid.java b/src/main/java/com/esri/core/geometry/Wkid.java index 9ecf2bd7..29b119f5 100644 --- a/src/main/java/com/esri/core/geometry/Wkid.java +++ b/src/main/java/com/esri/core/geometry/Wkid.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Wkt.java b/src/main/java/com/esri/core/geometry/Wkt.java index 08db42f1..a4c2a7f4 100644 --- a/src/main/java/com/esri/core/geometry/Wkt.java +++ b/src/main/java/com/esri/core/geometry/Wkt.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WktExportFlags.java b/src/main/java/com/esri/core/geometry/WktExportFlags.java index 2756135d..c9974be5 100644 --- a/src/main/java/com/esri/core/geometry/WktExportFlags.java +++ b/src/main/java/com/esri/core/geometry/WktExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WktImportFlags.java b/src/main/java/com/esri/core/geometry/WktImportFlags.java index 9bc57d6a..d16c2d20 100644 --- a/src/main/java/com/esri/core/geometry/WktImportFlags.java +++ b/src/main/java/com/esri/core/geometry/WktImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index 7e5afcb6..c3b4894e 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index f83d2569..8901df4e 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1200,5 +1200,4 @@ public void testReplaceNaNs() { } } - } diff --git a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java index a96528ca..a1ff65fc 100644 --- a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java +++ b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; @@ -130,5 +131,23 @@ public void test() { assertFalse(OperatorContains.local().execute(poly, new Point(1, 3), sr, null)); assertFalse(OperatorContains.local().execute(poly, new Point(1.6, 0.1), sr, null)); } + + /* + { + Geometry g = OperatorFactoryLocal.loadGeometryFromEsriShapeDbg("c:/temp/_poly_final.bin"); + RasterizedGeometry2D rg1 = RasterizedGeometry2D + .create(g, 0, 1024);//warmup + rg1 = null; + + long t0 = System.nanoTime(); + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(g, 0, 1024 * 1024); + long t1 = System.nanoTime(); + double d = (t1 - t0) / 1000000.0; + System.out.printf("Time to rasterize the geometry: %f", d); + + rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + for (;;){} + }*/ } } From 0570d128fd18099dd43deaf91ca4dcd12867c126 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 19 Feb 2015 17:40:58 -0800 Subject: [PATCH 008/116] a fix for #78 --- .../esri/core/geometry/RasterizedGeometry2DImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index 556a9fe0..ded79caf 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -213,11 +213,11 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, } int worldToPixX(double x) { - return (int) Math.round(x * m_dx + m_x0); + return (int) (x * m_dx + m_x0); } int worldToPixY(double y) { - return (int) Math.round(y * m_dy + m_y0); + return (int) (y * m_dy + m_y0); } RasterizedGeometry2DImpl(Geometry geom, double toleranceXY, @@ -405,6 +405,9 @@ void buildLevels() { @Override public HitType queryPointInGeometry(double x, double y) { + if (!m_geomEnv.contains(x, y)) + return HitType.Outside; + int ix = worldToPixX(x); int iy = worldToPixY(y); if (ix < 0 || ix >= m_width || iy < 0 || iy >= m_width) @@ -423,7 +426,8 @@ else if (res == 1) @Override public HitType queryEnvelopeInGeometry(Envelope2D env) { if (!env.intersect(m_geomEnv)) - return com.esri.core.geometry.RasterizedGeometry2D.HitType.Outside; + return HitType.Outside; + int ixmin = worldToPixX(env.xmin); int ixmax = worldToPixX(env.xmax); int iymin = worldToPixY(env.ymin); From 591acbe3871760ecf4626cf4321b198243af8ee3 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Fri, 20 Feb 2015 11:23:04 -0800 Subject: [PATCH 009/116] fixes for #79 and #80, made MultiPath.addEnvelope public --- src/main/java/com/esri/core/geometry/MultiPath.java | 2 +- .../esri/core/geometry/PairwiseIntersectorImpl.java | 2 +- .../com/esri/core/geometry/SimpleRasterizer.java | 2 +- src/main/java/com/esri/core/geometry/Wkid.java | 2 +- .../java/com/esri/core/geometry/TestRelation.java | 12 ++++++++++++ src/test/java/com/esri/core/geometry/TestWkid.java | 9 +++++++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index f2b80e89..8e778725 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -653,7 +653,7 @@ boolean hasNonLinearSegments(int pathIndex) { * @param bReverse * Creates reversed path. */ - void addEnvelope(Envelope2D envSrc, boolean bReverse) { + public void addEnvelope(Envelope2D envSrc, boolean bReverse) { m_impl.addEnvelope(envSrc, bReverse); } diff --git a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java index cb3c5c00..d02cc4fa 100644 --- a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java @@ -34,7 +34,7 @@ class PairwiseIntersectorImpl { private double m_tolerance; private int m_path_index; private int m_element_handle; - private Envelope2D m_paths_query; // only used for m_b_paths == true case + private Envelope2D m_paths_query = new Envelope2D(); // only used for m_b_paths == true case private QuadTreeImpl m_quad_tree; private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; private SegmentIteratorImpl m_seg_iter; diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 27a87351..783b8a8f 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -48,7 +48,7 @@ public class SimpleRasterizer { public static interface ScanCallback { /** * Rasterizer calls this method for each scan it produced - * @param scan array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), + * @param scans array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ diff --git a/src/main/java/com/esri/core/geometry/Wkid.java b/src/main/java/com/esri/core/geometry/Wkid.java index 29b119f5..9fd9cca0 100644 --- a/src/main/java/com/esri/core/geometry/Wkid.java +++ b/src/main/java/com/esri/core/geometry/Wkid.java @@ -146,7 +146,7 @@ public static double find_tolerance_from_wkid(int wkid) { if (tol == 1e38) { int old = wkid_to_old(wkid); if (old != wkid) - tol = find_tolerance_from_wkid_helper(wkid); + tol = find_tolerance_from_wkid_helper(old); if (tol == 1e38) return 1e-10; } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 10386f82..ec21049d 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -5481,4 +5481,16 @@ public void testCrosses_github_issue_40() { null); assertTrue(answer2); } + + @Test + public void testDisjointCrash() { + Polygon g1 = new Polygon(); + g1.addEnvelope(Envelope2D.construct(0, 0, 10, 10), false); + Polygon g2 = new Polygon(); + g2.addEnvelope(Envelope2D.construct(10, 1, 21, 21), false); + g1 = (Polygon)OperatorDensifyByLength.local().execute(g1, 0.1, null); + OperatorDisjoint.local().accelerateGeometry(g1, SpatialReference.create(4267), GeometryAccelerationDegree.enumHot); + boolean res = OperatorDisjoint.local().execute(g1, g2, SpatialReference.create(4267), null); + assertTrue(!res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index fc09a66c..d953ae95 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -19,4 +19,13 @@ public void test() { assertTrue(Math.abs(tol84 - 1e-8) < 1e-8 * 1e-8); } + @Test + public void test_80() { + SpatialReference sr = SpatialReference.create(3857); + assertTrue(sr.getID() == 3857); + assertTrue(sr.getLatestID() == 3857); + assertTrue(sr.getOldID() == 102100); + assertTrue(sr.getTolerance() == 0.001); + } + } From 1315745fe8da5a87dec8b3e83b5b05b2e0720f72 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:24:57 -0700 Subject: [PATCH 010/116] prepare for maven build --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 760d2734..d75b569d 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2 + 1.2.1-SNAPSHOT jar Esri Geometry API for Java From 5b28b728ac7c26ab8c804b79218eaf281981b122 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:26:17 -0700 Subject: [PATCH 011/116] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d75b569d..4f59fd07 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1-SNAPSHOT + 1.2.1 jar Esri Geometry API for Java From d4ade6b12134c9e74d5a1330e144c2cefc133e17 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:26:20 -0700 Subject: [PATCH 012/116] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f59fd07..4248a3ce 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1 + 1.2.2-SNAPSHOT jar Esri Geometry API for Java From 8b4be12076bc6268ee9e44c4d1411ddc5b92b429 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:51:31 -0700 Subject: [PATCH 013/116] add gpg sign activation --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4248a3ce..f85a8c4b 100755 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,13 @@ - release + release-sign-artifacts + + + performRelease + true + + From 9060e5e7a68b5c7a8d75daf16fd46a4bc578c75a Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 11:03:21 -0700 Subject: [PATCH 014/116] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f85a8c4b..7a72602c 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.2-SNAPSHOT + 1.2.1 jar Esri Geometry API for Java From e8eb988435197afb48d799721ea9bdf0bd74efb2 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 11:05:13 -0700 Subject: [PATCH 015/116] fighting with maven --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7a72602c..34b515a9 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1 + 1.2.1-SNAPSHOT jar Esri Geometry API for Java From f3632664ec296c68f94bf12cfb9e176472a2c70c Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 11:06:31 -0700 Subject: [PATCH 016/116] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 34b515a9..7a72602c 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1-SNAPSHOT + 1.2.1 jar Esri Geometry API for Java From 28c9a68e1c88a58b8ae07de3ab91ce9b2ef1c82f Mon Sep 17 00:00:00 2001 From: Eugen Stoianovici Date: Sun, 17 May 2015 11:38:24 +0100 Subject: [PATCH 017/116] added support for GeoJSON to geometry collections --- .../ogc/OGCConcreteGeometryCollection.java | 35 +++++++++++++++ .../esri/core/geometry/TestGeomToGeoJson.java | 45 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 4d91d7e2..b87a570a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -5,6 +5,11 @@ import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.JsonCursor; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.OperatorExportToGeoJson; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -323,4 +328,34 @@ public OGCGeometry convertToMulti() public String asJson() { throw new UnsupportedOperationException(); } + + @Override + public String asGeoJson() { + StringBuilder sb = new StringBuilder(); + + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + JsonCursor cursor = op.execute(this.esriSR, getEsriGeometryCursor()); + + sb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : "); + String shape = cursor.next(); + if (shape == null){ + // geometry collection with empty list of geometries + sb.append("[]}"); + return sb.toString(); + } + + sb.append("["); + sb.append(shape); + + while(true){ + shape = cursor.next(); + if(shape == null) + break; + sb.append(", ").append(shape); + } + + sb.append("]}"); + return sb.toString(); + } } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 80099069..9c517d39 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -27,6 +27,7 @@ import com.esri.core.geometry.ogc.OGCMultiPoint; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCPolygon; +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import junit.framework.TestCase; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; @@ -34,6 +35,8 @@ import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class TestGeomToGeoJson extends TestCase { OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); @@ -358,4 +361,46 @@ public void testEnvelopeGeometryEngine() { assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); } + @Test + public void testGeometryCollection(){ + SpatialReference sr = SpatialReference.create(4326); + + StringBuilder geometrySb = new StringBuilder(); + geometrySb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); + + OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); + assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", point.asJson()); + assertEquals("{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}", point.asGeoJson()); + geometrySb.append(point.asGeoJson()).append(", "); + + OGCLineString line = new OGCLineString(new Polyline(new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); + assertEquals("{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", line.asJson()); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[2.0,2.0]]}", line.asGeoJson()); + geometrySb.append(line.asGeoJson()).append(", "); + + Polygon p = new Polygon(); + p.startPath(1.0, 1.0); + p.lineTo(2.0, 2.0); + p.lineTo(3.0, 1.0); + p.lineTo(2.0, 0.0); + + OGCPolygon polygon = new OGCPolygon(p, sr); + assertEquals("{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", + polygon.asJson()); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[2.0,2.0],[3.0,1.0],[2.0,0.0],[1.0,1.0]]]}", + polygon.asGeoJson()); + geometrySb.append(polygon.asGeoJson()).append("]}"); + + List geoms = new ArrayList(3); + geoms.add(point);geoms.add(line);geoms.add(polygon); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(geoms, sr); + assertEquals(geometrySb.toString(), collection.asGeoJson()); + } + + @Test + public void testEmptyGeometryCollection(){ + SpatialReference sr = SpatialReference.create(4326); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(new ArrayList(), sr); + assertEquals("{\"type\" : \"GeometryCollection\", \"geometries\" : []}", collection.asGeoJson()); + } } From d6660b1628c2e4109c19f7af369604e52975dda8 Mon Sep 17 00:00:00 2001 From: Alex Panchenko Date: Wed, 23 Sep 2015 19:08:54 +0500 Subject: [PATCH 018/116] 1.2.1 maven version in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03d91b99..033b44c3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 1.2 + 1.2.1 ``` From fdf3c496b780e072c74988aa99c56758c136b70f Mon Sep 17 00:00:00 2001 From: Alex Panchenko Date: Wed, 30 Sep 2015 13:47:29 +0600 Subject: [PATCH 019/116] Fix for SpatialReferenceImpl.equals(Object) - compare different values --- .../core/geometry/SpatialReferenceImpl.java | 2 +- .../geometry/TestSpatialReferenceImpl.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 772b8ee7..77520c40 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -218,7 +218,7 @@ public boolean equals(Object obj) { return false; if (m_userWkid == 0) { - if (!m_userWkt.equals(m_userWkt))// m_userWkt cannot be null here! + if (!m_userWkt.equals(sr.m_userWkt))// m_userWkt cannot be null here! return false; } diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java b/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java new file mode 100644 index 00000000..61c83615 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java @@ -0,0 +1,24 @@ +package com.esri.core.geometry; + +import org.junit.Assert; +import org.junit.Test; + +public class TestSpatialReferenceImpl extends Assert { + @Test + public void equals() { + final String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; + final String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; + + final SpatialReference a1 = SpatialReference.create(wktext1); + final SpatialReference b = SpatialReference.create(wktext2); + final SpatialReference a2 = SpatialReference.create(wktext1); + + assertTrue(a1.equals(a1)); + assertTrue(b.equals(b)); + + assertTrue(a1.equals(a2)); + + assertFalse(a1.equals(b)); + assertFalse(b.equals(a1)); + } +} From b17ee469eeb706bf343fe1d7e82e14ea71e07520 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 30 Sep 2015 10:22:32 -0700 Subject: [PATCH 020/116] Update and rename TestSpatialReferenceImpl.java to TestSpatialReference.java --- ...{TestSpatialReferenceImpl.java => TestSpatialReference.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/esri/core/geometry/{TestSpatialReferenceImpl.java => TestSpatialReference.java} (95%) diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java similarity index 95% rename from src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java rename to src/test/java/com/esri/core/geometry/TestSpatialReference.java index 61c83615..f0f6aacd 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -3,7 +3,7 @@ import org.junit.Assert; import org.junit.Test; -public class TestSpatialReferenceImpl extends Assert { +public class TestSpatialReference extends Assert { @Test public void equals() { final String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; From 4b949c777660a533b04a50c2e6c61e71d2abbab8 Mon Sep 17 00:00:00 2001 From: Oliver Snowden Date: Sun, 11 Oct 2015 01:12:38 +0100 Subject: [PATCH 021/116] bump to latest dependency versions Specifically, dependency versions that work with a 1.6 JDK. Note: later org.json:json versions only work with Java 1.8. JDK 8: 20141113 20150729 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7a72602c..88357799 100755 --- a/pom.xml +++ b/pom.xml @@ -89,9 +89,9 @@ 1.6 - 20090211 - 1.9.12 - 4.11 + 20140107 + 1.9.13 + 4.12 2.3.1 From 33a1c3cf6bcaee19db268bb2fdfadd2f533cb8a2 Mon Sep 17 00:00:00 2001 From: David Raleigh Date: Sat, 14 Nov 2015 13:19:39 +0100 Subject: [PATCH 022/116] digits on either side of decimal --- .../java/com/esri/core/geometry/ECoordinate.java | 4 ++-- src/main/java/com/esri/core/geometry/GeoDist.java | 2 +- .../com/esri/core/geometry/Transformation2D.java | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java index 253a05b6..c3d5c522 100644 --- a/src/main/java/com/esri/core/geometry/ECoordinate.java +++ b/src/main/java/com/esri/core/geometry/ECoordinate.java @@ -189,7 +189,7 @@ void div(ECoordinate divis) { if (divis.m_eps > 0.01 * fabsdivis) {// more accurate error calculation // for very inaccurate divisor double rr = divis.m_eps / fabsdivis; - e *= (1. + (1. + rr) * rr); + e *= (1.0 + (1.0 + rr) * rr); } m_value = r; m_eps = e + epsCoordinate() * Math.abs(r); @@ -226,7 +226,7 @@ void sqrt() { if (m_value >= 0) { // assume non-negative input r = Math.sqrt(m_value); - if (m_value > 10. * m_eps) { + if (m_value > 10.0 * m_eps) { dr = 0.5 * m_eps / r; } else { dr = (m_value > m_eps) ? r - Math.sqrt(m_value - m_eps) : Math diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 81225134..108e04e0 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -88,7 +88,7 @@ static private double q90(double a, double e2) { double n2 = n * n; return a / (1.0 + n) - * (1.0 + n2 * (1. / 4. + n2 * (1. / 64. + n2 * (1. / 256.)))) + * (1.0 + n2 * (1.0 / 4.0 + n2 * (1.0 / 64.0 + n2 * (1.0 / 256.0)))) * PE_PI2; } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index f5d07a54..d1472399 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -456,9 +456,9 @@ public boolean isIdentity() { * The tolerance value. */ public boolean isIdentity(double tol) { - Point2D pt = Point2D.construct(0., 1.); + Point2D pt = Point2D.construct(0.0, 1.0); transform(pt, pt); - pt.sub(Point2D.construct(0., 1.)); + pt.sub(Point2D.construct(0.0, 1.0)); if (pt.sqrLength() > tol * tol) return false; @@ -467,9 +467,9 @@ public boolean isIdentity(double tol) { if (pt.sqrLength() > tol * tol) return false; - pt.setCoords(1., 0.); + pt.setCoords(1.0, 0.0); transform(pt, pt); - pt.sub(Point2D.construct(1., 0)); + pt.sub(Point2D.construct(1.0, 0)); return pt.sqrLength() <= tol * tol; } @@ -510,12 +510,12 @@ public boolean isShift() { * The tolerance value. */ public boolean isShift(double tol) { - Point2D pt = transformWithoutShift(Point2D.construct(0., 1.)); + Point2D pt = transformWithoutShift(Point2D.construct(0.0, 1.0)); pt.y -= 1.0; if (pt.sqrLength() > tol * tol) return false; - pt = transformWithoutShift(Point2D.construct(1., 0.)); + pt = transformWithoutShift(Point2D.construct(1.0, 0.0)); pt.x -= 1.0; return pt.sqrLength() <= tol * tol; } From 96f83f4eea0965a0198816098ce76e7d299be97e Mon Sep 17 00:00:00 2001 From: David Raleigh Date: Sat, 14 Nov 2015 14:36:03 +0100 Subject: [PATCH 023/116] fix @Test attribute placements --- src/test/java/com/esri/core/geometry/TestClip.java | 5 ----- src/test/java/com/esri/core/geometry/TestWktParser.java | 7 +++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index ee8d675f..0149d482 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -107,7 +107,6 @@ public static void testClipGeometries() { } } - @Test public static Polygon makePolygon() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -117,7 +116,6 @@ public static Polygon makePolygon() { return poly; } - @Test public static Polyline makePolyline() { Polyline poly = new Polyline(); poly.startPath(0, 0); @@ -185,7 +183,6 @@ public static void testArcObjectsFailureCR196492() { // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); } - @Test public static Polyline makePolylineCR() { Polyline polyline = new Polyline(); @@ -200,7 +197,6 @@ public static Polyline makePolylineCR() { return polyline; } - @Test public static MultiPoint makeMultiPoint() { MultiPoint mpoint = new MultiPoint(); @@ -223,7 +219,6 @@ public static MultiPoint makeMultiPoint() { return mpoint; } - @Test public static Point makePoint() { Point point = new Point(); diff --git a/src/test/java/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java index 9949e5a3..db40cdff 100644 --- a/src/test/java/com/esri/core/geometry/TestWktParser.java +++ b/src/test/java/com/esri/core/geometry/TestWktParser.java @@ -7,6 +7,7 @@ public class TestWktParser extends TestCase { + @Test public void testGeometryCollection() { String s = " geometrycollection emPty "; WktParser wktParser = new WktParser(); @@ -136,6 +137,7 @@ public void testGeometryCollection() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testMultiPolygon() { String s = " MultIPolYgOn emPty "; WktParser wktParser = new WktParser(); @@ -413,6 +415,7 @@ public void testMultiPolygon() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testMultiLineString() { String s = " MultiLineString emPty "; WktParser wktParser = new WktParser(); @@ -623,6 +626,7 @@ public void testMultiLineString() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testMultiPoint() { String s = " MultipoInt emPty "; WktParser wktParser = new WktParser(); @@ -758,6 +762,7 @@ public void testMultiPoint() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testPolygon() { String s = " Polygon emPty "; WktParser wktParser = new WktParser(); @@ -968,6 +973,7 @@ public void testPolygon() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testLineString() { String s = " LineString emPty "; WktParser wktParser = new WktParser(); @@ -1022,6 +1028,7 @@ public void testLineString() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testPoint() { String s = " PoInT emPty "; WktParser wktParser = new WktParser(); From 6c3d5955ac99dff5e39adedce49b32414f8b8fba Mon Sep 17 00:00:00 2001 From: serg4066 Date: Fri, 18 Dec 2015 09:48:08 -0800 Subject: [PATCH 024/116] fix a bug in generalize for large deviations --- .../geometry/OperatorGeneralizeCursor.java | 6 +++--- .../esri/core/geometry/TestGeneralize.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 2b1d5ba1..5efc066d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -64,8 +64,6 @@ private Geometry Generalize(Geometry geom) { if (geom.isEmpty()) return geom; MultiPath mp = (MultiPath) geom; - if (mp == null) - throw GeometryException.GeometryInternalError(); MultiPath dstmp = (MultiPath) geom.createInstance(); Line line = new Line(); for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { @@ -113,7 +111,9 @@ private void GeneralizePath(MultiPathImpl mpsrc, int ipath, if (!bClosed) resultStack.add(stack.get(0)); - if (resultStack.size() == stack.size()) { + int rs_size = resultStack.size(); + int path_size = mpsrc.getPathSize(ipath); + if (rs_size == path_size && rs_size == stack.size()) { mpdst.addPath(mpsrc, ipath, true); } else { if (resultStack.size() >= 2) { diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index c7316e74..ced42b23 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; public class TestGeneralize extends TestCase { @@ -90,4 +91,24 @@ public static void test2() { assertTrue(points[0].x == 0 && points[0].y == 0); assertTrue(points[1].x == 0 && points[1].y == 10); } + + @Test + public static void testLargeDeviation() { + { + Polygon input_polygon = new Polygon(); + input_polygon + .addEnvelope(Envelope2D.construct(0, 0, 20, 10), false); + Geometry densified_geom = OperatorDensifyByLength.local().execute( + input_polygon, 1, null); + Geometry geom = OperatorGeneralize.local().execute(densified_geom, + 1, true, null); + int pc = ((MultiPath) geom).getPointCount(); + assertTrue(pc == 4); + + Geometry large_dev = OperatorGeneralize.local().execute( + densified_geom, 40, true, null); + int pc1 = ((MultiPath) large_dev).getPointCount(); + assertTrue(pc1 == 0); + } + } } From e7b03a982727be809515dcc2e330cb6fdd4d9b49 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Mon, 28 Dec 2015 14:26:53 -0800 Subject: [PATCH 025/116] make sure to leave degenerate rings when requested --- .../esri/core/geometry/OperatorGeneralizeCursor.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 5efc066d..b0b31a5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -116,16 +116,18 @@ private void GeneralizePath(MultiPathImpl mpsrc, int ipath, if (rs_size == path_size && rs_size == stack.size()) { mpdst.addPath(mpsrc, ipath, true); } else { - if (resultStack.size() >= 2) { - if (m_bRemoveDegenerateParts && resultStack.size() == 2) { - if (bClosed) + if (resultStack.size() > 0) { + if (m_bRemoveDegenerateParts && resultStack.size() <= 2) { + if (bClosed || resultStack.size() == 1) return; + double d = Point2D.distance( mpsrc.getXY(resultStack.get(0)), mpsrc.getXY(resultStack.get(1))); if (d <= m_maxDeviation) return; } + Point point = new Point(); for (int i = 0, n = resultStack.size(); i < n; i++) { mpsrc.getPointByVal(resultStack.get(i), point); @@ -136,8 +138,9 @@ private void GeneralizePath(MultiPathImpl mpsrc, int ipath, } if (bClosed) { - if (!m_bRemoveDegenerateParts && resultStack.size() == 2) + for (int i = resultStack.size(); i < 3; i++) mpdst.lineTo(point); + mpdst.closePathWithLine(); } } From ce99bc5688094138cc9ae49a874ff7fe62b24dce Mon Sep 17 00:00:00 2001 From: serg4066 Date: Mon, 28 Dec 2015 15:36:36 -0800 Subject: [PATCH 026/116] added a test for generalize case --- src/test/java/com/esri/core/geometry/TestGeneralize.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index ced42b23..5348d20d 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -105,10 +105,15 @@ public static void testLargeDeviation() { int pc = ((MultiPath) geom).getPointCount(); assertTrue(pc == 4); - Geometry large_dev = OperatorGeneralize.local().execute( + Geometry large_dev1 = OperatorGeneralize.local().execute( densified_geom, 40, true, null); - int pc1 = ((MultiPath) large_dev).getPointCount(); + int pc1 = ((MultiPath) large_dev1).getPointCount(); assertTrue(pc1 == 0); + + Geometry large_dev2 = OperatorGeneralize.local().execute( + densified_geom, 40, false, null); + int pc2 = ((MultiPath) large_dev2).getPointCount(); + assertTrue(pc2 == 3); } } } From 20224f98acd3c84d6364205ae127cca405776934 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 4 Jan 2016 10:13:48 -0800 Subject: [PATCH 027/116] README: update copyright to 2016 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 033b44c3..535a2da9 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2015 Esri +Copyright 2013-2016 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -A copy of the license is available in the repository's [license.txt]( https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. +A copy of the license is available in the repository's [license.txt](https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. [](Esri Tags: ArcGIS, Java, Geometry, Relationship, Analysis, JSON, WKT, Shape) [](Esri Language: Java) From 48cf546d25b44ff7bb8ecda64fe0e30fbb384623 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 6 Jan 2016 14:57:23 -0800 Subject: [PATCH 028/116] fix geodist hang --- .../java/com/esri/core/geometry/GeoDist.java | 2 +- .../com/esri/core/geometry/TestGeodetic.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 108e04e0..b7845151 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -257,7 +257,7 @@ static public void geodesic_distance_ngs(double a, double e2, double lam1, /* top of the long-line loop (kind = 1) */ q_continue_looping = true; - while (q_continue_looping == true) { + while (q_continue_looping && it < 100) { it = it + 1; if (kind == 1) { diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 0cdbf6f7..9f0b9ed6 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -50,6 +50,37 @@ public void testRotationInvariance() { } } + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + @Test public void testLengthAccurateCR191313() { /* From df0ce3b83daa29ca78b391f84bcdc71327180fb4 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 6 Jan 2016 15:00:57 -0800 Subject: [PATCH 029/116] small change how point coords are passed --- .../java/com/esri/core/geometry/SpatialReferenceImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 77520c40..32091d43 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; - import java.lang.ref.*; import com.esri.core.geometry.Envelope2D; @@ -230,8 +229,8 @@ static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 double rpu = Math.PI / 180.0; PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getXY().x * rpu, - ptFrom.getXY().y * rpu, ptTo.getXY().x * rpu, ptTo.getXY().y + GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getX() * rpu, + ptFrom.getY() * rpu, ptTo.getX() * rpu, ptTo.getY() * rpu, answer, null, null); return answer.val; } From d9a9a3c2149b80ff438a1e46e501fa3828c9591a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 10 Mar 2016 15:45:44 -0800 Subject: [PATCH 030/116] Update MultiVertexGeometryImpl.java Don't crash on toString() done on Impl classes. #112 --- .../java/com/esri/core/geometry/MultiVertexGeometryImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 11ef7ed4..0c92a930 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -1110,6 +1110,9 @@ public abstract boolean _buildRasterizedGeometryAccelerator( public abstract boolean _buildQuadTreeAccelerator( GeometryAccelerationDegree d); - // //////////////////METHODS To REMOVE /////////////////////// + @Override + public String toString() { + return "MultiVertexGeometryImpl"; + } } From d08e90ccabadf00875887352e6aaadd9b99eaa34 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 4 May 2016 09:49:13 -0700 Subject: [PATCH 031/116] buffer fix, geojson reqrite, more public methods --- .gitignore | 1 + .../core/geometry/AttributeStreamOfDbl.java | 20 +- .../java/com/esri/core/geometry/Bufferer.java | 895 ++++++--- .../com/esri/core/geometry/ConvexHull.java | 432 +++-- .../com/esri/core/geometry/ECoordinate.java | 14 +- .../com/esri/core/geometry/EditShape.java | 8 + .../java/com/esri/core/geometry/EnvSrlzr.java | 95 + .../java/com/esri/core/geometry/Envelope.java | 45 +- .../com/esri/core/geometry/Envelope1D.java | 10 +- .../com/esri/core/geometry/Envelope2D.java | 119 +- .../geometry/Envelope2DIntersectorImpl.java | 188 +- .../com/esri/core/geometry/Envelope3D.java | 145 +- .../geometry/GenericGeometrySerializer.java | 94 + .../java/com/esri/core/geometry/GeoDist.java | 2 +- .../esri/core/geometry/GeoJsonCrsTables.java | 183 ++ .../core/geometry/GeoJsonExportFlags.java | 29 +- .../core/geometry/GeoJsonImportFlags.java | 13 +- .../java/com/esri/core/geometry/Geometry.java | 44 +- .../esri/core/geometry/GeometryEngine.java | 1 + .../core/geometry/GeometrySerializer.java | 2 + .../esri/core/geometry/IntervalTreeImpl.java | 1708 ++++++++--------- .../core/geometry/JsonGeometryException.java | 40 + .../esri/core/geometry/JsonParserReader.java | 17 +- .../com/esri/core/geometry/JsonReader.java | 16 +- .../esri/core/geometry/JsonStringWriter.java | 58 +- .../esri/core/geometry/JsonValueReader.java | 17 +- .../com/esri/core/geometry/JsonWriter.java | 16 +- .../java/com/esri/core/geometry/Line.java | 14 +- .../java/com/esri/core/geometry/LnSrlzr.java | 93 + .../com/esri/core/geometry/MathUtils.java | 28 +- .../com/esri/core/geometry/MultiPath.java | 43 +- .../com/esri/core/geometry/MultiPathImpl.java | 25 +- .../com/esri/core/geometry/MultiPoint.java | 19 +- .../core/geometry/MultiVertexGeometry.java | 4 +- .../geometry/MultiVertexGeometryImpl.java | 11 +- .../com/esri/core/geometry/NumberUtils.java | 9 + .../esri/core/geometry/OperatorBuffer.java | 25 + .../core/geometry/OperatorBufferCursor.java | 18 +- .../core/geometry/OperatorBufferLocal.java | 27 +- .../geometry/OperatorConvexHullCursor.java | 127 +- .../geometry/OperatorExportToGeoJson.java | 60 +- .../OperatorExportToGeoJsonCursor.java | 1080 ++++++++--- .../OperatorExportToGeoJsonLocal.java | 51 +- .../geometry/OperatorExportToJsonCursor.java | 860 +++++---- .../core/geometry/OperatorFactoryLocal.java | 18 + .../core/geometry/OperatorGeodesicBuffer.java | 8 +- .../OperatorGeodeticDensifyByLength.java | 2 +- .../geometry/OperatorImportFromGeoJson.java | 39 +- .../OperatorImportFromGeoJsonLocal.java | 1581 ++++++++++++--- .../OperatorImportFromJsonCursor.java | 1 - .../core/geometry/OperatorIntersection.java | 6 +- .../OperatorShapePreservingDensify.java | 4 +- .../java/com/esri/core/geometry/Point.java | 27 +- .../java/com/esri/core/geometry/Point2D.java | 294 ++- .../java/com/esri/core/geometry/Point3D.java | 68 +- .../java/com/esri/core/geometry/Polygon.java | 4 +- .../com/esri/core/geometry/PolygonUtils.java | 53 +- .../java/com/esri/core/geometry/Polyline.java | 4 +- .../com/esri/core/geometry/PolylinePath.java | 77 - .../java/com/esri/core/geometry/PtSrlzr.java | 88 + .../java/com/esri/core/geometry/QuadTree.java | 272 ++- .../com/esri/core/geometry/QuadTreeImpl.java | 1053 +++++++--- .../geometry/RasterizedGeometry2DImpl.java | 61 +- .../java/com/esri/core/geometry/Segment.java | 67 +- .../esri/core/geometry/SegmentIterator.java | 12 +- .../esri/core/geometry/SimpleRasterizer.java | 44 + .../core/geometry/SpatialReferenceImpl.java | 72 +- .../esri/core/geometry/Transformation2D.java | 12 +- .../esri/core/geometry/Transformation3D.java | 27 +- .../esri/core/geometry/VertexDescription.java | 255 ++- .../VertexDescriptionDesignerImpl.java | 236 +-- .../core/geometry/VertexDescriptionHash.java | 72 +- .../com/esri/core/geometry/WktParser.java | 2 +- .../ogc/OGCConcreteGeometryCollection.java | 103 +- .../esri/core/geometry/ogc/OGCGeometry.java | 63 +- .../esri/core/geometry/new_to_old_wkid.txt | 1 + .../core/geometry/pcs_id_to_tolerance.txt | 1 + .../java/com/esri/core/geometry/TestClip.java | 10 +- .../esri/core/geometry/TestConvexHull.java | 365 ++-- .../esri/core/geometry/TestDifference.java | 4 + .../com/esri/core/geometry/TestGeodetic.java | 37 +- .../esri/core/geometry/TestGeomToGeoJson.java | 795 ++++---- .../esri/core/geometry/TestImportExport.java | 1054 +++++----- .../esri/core/geometry/TestIntersection.java | 46 + .../java/com/esri/core/geometry/TestOGC.java | 27 +- .../com/esri/core/geometry/TestPolygon.java | 104 +- .../com/esri/core/geometry/TestQuadTree.java | 361 +++- .../com/esri/core/geometry/TestRelation.java | 16 +- .../esri/core/geometry/TestSerialization.java | 180 +- .../core/geometry/TestSpatialReference.java | 17 +- .../java/com/esri/core/geometry/TestWkid.java | 1 + .../com/esri/core/geometry/savedEnvelope1.txt | Bin 0 -> 147 bytes .../esri/core/geometry/savedMultiPoint1.txt | Bin 0 -> 254 bytes .../com/esri/core/geometry/savedPoint1.txt | Bin 0 -> 138 bytes .../com/esri/core/geometry/savedPolygon1.txt | Bin 0 -> 317 bytes .../com/esri/core/geometry/savedPolyline1.txt | Bin 0 -> 301 bytes 96 files changed, 9314 insertions(+), 5036 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/EnvSrlzr.java create mode 100644 src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java create mode 100644 src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java create mode 100644 src/main/java/com/esri/core/geometry/JsonGeometryException.java create mode 100644 src/main/java/com/esri/core/geometry/LnSrlzr.java delete mode 100644 src/main/java/com/esri/core/geometry/PolylinePath.java create mode 100644 src/main/java/com/esri/core/geometry/PtSrlzr.java create mode 100644 src/test/resources/com/esri/core/geometry/savedEnvelope1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedMultiPoint1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedPoint1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedPolygon1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedPolyline1.txt diff --git a/.gitignore b/.gitignore index 85898c5a..f31512c4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ Icon? ehthumbs.db Thumbs.db target/* +/bin/ diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 18ae0fc1..bbf40fde 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -89,7 +89,7 @@ public AttributeStreamOfDbl(AttributeStreamOfDbl other, int maxSize) { /** * Reads a value from the buffer at given offset. - * + * * @param offset * is the element number in the stream. */ @@ -103,7 +103,7 @@ public double get(int offset) { /** * Overwrites given element with new value. - * + * * @param offset * is the element number in the stream. * @param value @@ -125,7 +125,7 @@ public void set(int offset, double value) { /** * Reads a value from the buffer at given offset. - * + * * @param offset * is the element number in the stream. */ @@ -136,7 +136,7 @@ public void read(int offset, Point2D outPoint) { /** * Overwrites given element with new value. - * + * * @param offset * is the element number in the stream. * @param value @@ -213,7 +213,7 @@ public void resize(int newSize) { if (newSize <= m_size) { if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded + // margin is exceeded double[] newBuffer = new double[newSize]; System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); m_buffer = newBuffer; @@ -251,7 +251,7 @@ public void resize(int newSize, double defaultValue) { "invalid call. Attribute Stream is locked and cannot be resized."); if (newSize <= m_size) { if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded + // margin is exceeded double[] newBuffer = new double[newSize]; System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); m_buffer = newBuffer; @@ -579,8 +579,8 @@ public void eraseRange(int index, int count, int validSize) { throw new GeometryException("invalid_call"); if (validSize - (index + count) > 0) { - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); + System.arraycopy(m_buffer, index + count, m_buffer, index, + validSize - (index + count)); } m_size -= count; } @@ -656,8 +656,8 @@ public void writeRange(int startElement, int count, throw new IllegalArgumentException(); AttributeStreamOfDbl src = (AttributeStreamOfDbl) _src; // the input - // type must - // match + // type must + // match if (src.size() < (int) (srcStart + count)) throw new IllegalArgumentException(); diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index ebe961ce..b7008559 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -24,13 +24,32 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.List; class Bufferer { + Bufferer() { + m_buffer_commands = new ArrayList(128); + m_progress_tracker = null; + m_tolerance = 0; + m_small_tolerance = 0; + m_filter_tolerance = 0; + m_distance = 0; + m_original_geom_type = Geometry.GeometryType.Unknown; + m_abs_distance_reversed = 0; + m_abs_distance = 0; + m_densify_dist = -1; + m_dA = -1; + m_b_output_loops = true; + m_bfilter = true; + m_old_circle_template_size = 0; + } + + /** * Result is always a polygon. For non positive distance and non-areas * returns an empty polygon. For points returns circles. */ - static Geometry buffer(Geometry geometry, double distance, + Geometry buffer(Geometry geometry, double distance, SpatialReference sr, double densify_dist, int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { if (geometry == null) @@ -47,34 +66,36 @@ static Geometry buffer(Geometry geometry, double distance, if (distance > 0) env2D.inflate(distance, distance); - Bufferer bufferer = new Bufferer(progress_tracker); - bufferer.m_spatialReference = sr; - bufferer.m_geometry = geometry; - bufferer.m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + m_progress_tracker = progress_tracker; + + m_original_geom_type = geometry.getType().value(); + m_geometry = geometry; + m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, env2D, true);// conservative to have same effect as simplify - bufferer.m_small_tolerance = InternalUtils + m_small_tolerance = InternalUtils .calculateToleranceFromGeometry(null, env2D, true);// conservative // to have // same // effect as // simplify - bufferer.m_distance = distance; - bufferer.m_original_geom_type = geometry.getType().value(); + if (max_vertex_in_complete_circle <= 0) { max_vertex_in_complete_circle = 96;// 96 is the value used by SG. // This is the number of // vertices in the full circle. } - - bufferer.m_abs_distance = Math.abs(bufferer.m_distance); - bufferer.m_abs_distance_reversed = bufferer.m_abs_distance != 0 ? 1.0 / bufferer.m_abs_distance + + m_spatialReference = sr; + m_distance = distance; + m_abs_distance = Math.abs(m_distance); + m_abs_distance_reversed = m_abs_distance != 0 ? 1.0 / m_abs_distance : 0; if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { - densify_dist = bufferer.m_abs_distance * 1e-5; + densify_dist = m_abs_distance * 1e-5; } else { - if (densify_dist > bufferer.m_abs_distance * 0.5) - densify_dist = bufferer.m_abs_distance * 0.5;// do not allow too + if (densify_dist > m_abs_distance * 0.5) + densify_dist = m_abs_distance * 0.5;// do not allow too // large densify // distance (the // value will be @@ -85,6 +106,7 @@ static Geometry buffer(Geometry geometry, double distance, if (max_vertex_in_complete_circle < 12) max_vertex_in_complete_circle = 12; + double max_dd = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); @@ -105,13 +127,26 @@ static Geometry buffer(Geometry geometry, double distance, } } - bufferer.m_densify_dist = densify_dist; - bufferer.m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + m_densify_dist = densify_dist; + m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; // when filtering close points we do not want the filter to distort // generated buffer too much. - bufferer.m_filter_tolerance = Math.min(bufferer.m_small_tolerance, + m_filter_tolerance = Math.min(m_small_tolerance, densify_dist * 0.25); - return bufferer.buffer_(); + + + m_circle_template_size = calcN_(); + if (m_circle_template_size != m_old_circle_template_size) { + // we have an optimization for this method to be called several + // times. Here we detected too many changes and need to regenerate + // the data. + m_circle_template.clear(); + m_old_circle_template_size = m_circle_template_size; + } + + Geometry result_geom = buffer_(); + m_geometry = null; + return result_geom; } private Geometry m_geometry; @@ -120,8 +155,6 @@ private static final class BufferCommand { private interface Flags { static final int enum_line = 1; static final int enum_arc = 2; - static final int enum_dummy = 4; - static final int enum_concave_dip = 8; static final int enum_connection = enum_arc | enum_line; } @@ -175,22 +208,22 @@ private BufferCommand(Point2D from, Point2D to, int next, int prev, private double m_dA; private boolean m_b_output_loops; private boolean m_bfilter; - private ArrayList m_circle_template; + private ArrayList m_circle_template = new ArrayList(0); private ArrayList m_left_stack; private ArrayList m_middle_stack; private Line m_helper_line_1; private Line m_helper_line_2; private Point2D[] m_helper_array; private int m_progress_counter; + private int m_circle_template_size; + private int m_old_circle_template_size; private void generateCircleTemplate_() { - if (m_circle_template == null) { - m_circle_template = new ArrayList(0); - } else if (!m_circle_template.isEmpty()) { + if (!m_circle_template.isEmpty()) { return; } - int N = calcN_(4); + int N = m_circle_template_size; assert (N >= 4); int real_size = (N + 3) / 4; @@ -218,6 +251,7 @@ private void generateCircleTemplate_() { private static final class GeometryCursorForMultiPoint extends GeometryCursor { + private Bufferer m_parent; private int m_index; private Geometry m_buffered_polygon; private MultiPoint m_mp; @@ -229,10 +263,11 @@ private static final class GeometryCursorForMultiPoint extends private int m_max_vertex_in_complete_circle; private ProgressTracker m_progress_tracker; - GeometryCursorForMultiPoint(MultiPoint mp, double distance, + GeometryCursorForMultiPoint(Bufferer parent, MultiPoint mp, double distance, SpatialReference sr, double densify_dist, int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { + m_parent = parent; m_index = 0; m_mp = mp; m_x = 0; @@ -263,7 +298,7 @@ public Geometry next() { m_x = point.getX(); m_y = point.getY(); - m_buffered_polygon = Bufferer.buffer(point, m_distance, + m_buffered_polygon = m_parent.buffer(point, m_distance, m_spatialReference, m_densify_dist, m_max_vertex_in_complete_circle, m_progress_tracker); b_first = true; @@ -295,64 +330,113 @@ public int getGeometryID() { } } - private static final class GeometryCursorForPolyline extends GeometryCursor { - private Bufferer m_bufferer; - private int m_index; - private boolean m_bfilter; + private static final class GlueingCursorForPolyline extends GeometryCursor { + private Polyline m_polyline; + private int m_current_path_index; - GeometryCursorForPolyline(Bufferer bufferer, boolean bfilter) { - m_bufferer = bufferer; - m_index = 0; - m_bfilter = bfilter; + GlueingCursorForPolyline(Polyline polyline) { + m_polyline = polyline; + m_current_path_index = 0; } @Override public Geometry next() { - MultiPathImpl mp = (MultiPathImpl) (m_bufferer.m_geometry - ._getImpl()); - if (m_index < mp.getPathCount()) { - int ind = m_index; - m_index++; + if (m_polyline == null) + return null; + + MultiPathImpl mp = (MultiPathImpl) m_polyline._getImpl(); + int npaths = mp.getPathCount(); + if (m_current_path_index < npaths) { + int ind = m_current_path_index; + m_current_path_index++; if (!mp.isClosedPathInXYPlane(ind)) { + // connect paths that follow one another as an optimization + // for buffering (helps when one polyline is split into many + // segments). Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); - while (m_index < mp.getPathCount()) { - Point2D start = mp.getXY(mp.getPathStart(m_index)); - if (mp.isClosedPathInXYPlane(m_index)) + while (m_current_path_index < mp.getPathCount()) { + Point2D start = mp.getXY(mp + .getPathStart(m_current_path_index)); + if (mp.isClosedPathInXYPlane(m_current_path_index)) break; if (start != prev_end) break; - prev_end = mp.getXY(mp.getPathEnd(m_index) - 1); - m_index++; + prev_end = mp + .getXY(mp.getPathEnd(m_current_path_index) - 1); + m_current_path_index++; } } - if (m_index - ind == 1) - return m_bufferer.bufferPolylinePath_( - (Polyline) (m_bufferer.m_geometry), ind, m_bfilter); - else { - Polyline tmp_polyline = new Polyline( - m_bufferer.m_geometry.getDescription()); - tmp_polyline.addPath((Polyline) (m_bufferer.m_geometry), - ind, true); - for (int i = ind + 1; i < m_index; i++) { - ((MultiPathImpl) tmp_polyline._getImpl()) - .addSegmentsFromPath( - (MultiPathImpl) m_bufferer.m_geometry - ._getImpl(), i, 0, mp - .getSegmentCount(i), false); - } - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_ppp.txt", - // tmp_polyline, nullptr); - Polygon res = m_bufferer.bufferPolylinePath_(tmp_polyline, - 0, m_bfilter); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_ppp_res.txt", - // *res, nullptr); - return res; + if (ind == 0 + && m_current_path_index == m_polyline.getPathCount()) { + Polyline pol = m_polyline; + m_polyline = null; + return pol; } + + Polyline tmp_polyline = new Polyline( + m_polyline.getDescription()); + tmp_polyline.addPath(m_polyline, ind, true); + for (int i = ind + 1; i < m_current_path_index; i++) { + tmp_polyline.addSegmentsFromPath(m_polyline, i, 0, + mp.getSegmentCount(i), false); + } + + if (false) { + OperatorFactoryLocal.saveGeometryToEsriShapeDbg( + "c:/temp/_geom.bin", tmp_polyline); + } + + if (m_current_path_index == m_polyline.getPathCount()) + m_polyline = null; + + return tmp_polyline; + } else { + return null; } + } - return null; + @Override + public int getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolyline extends GeometryCursor { + private Bufferer m_bufferer; + GeometryCursor m_geoms; + Geometry m_geometry; + private int m_index; + private boolean m_bfilter; + + GeometryCursorForPolyline(Bufferer bufferer, GeometryCursor geoms, + boolean bfilter) { + m_bufferer = bufferer; + m_geoms = geoms; + m_index = 0; + m_bfilter = bfilter; + } + + @Override + public Geometry next() { + if (m_geometry == null) { + m_index = 0; + m_geometry = m_geoms.next(); + if (m_geometry == null) + return null; + } + + MultiPath mp = (MultiPath) (m_geometry); + if (m_index < mp.getPathCount()) { + int ind = m_index; + m_index++; + return m_bufferer.bufferPolylinePath_((Polyline) m_geometry, + ind, m_bfilter); + } + + m_geometry = null; + return next(); } @Override @@ -404,22 +488,6 @@ public int getGeometryID() { } } - private Bufferer(ProgressTracker progress_tracker) { - m_buffer_commands = new ArrayList(0); - m_progress_tracker = progress_tracker; - m_tolerance = 0; - m_small_tolerance = 0; - m_filter_tolerance = 0; - m_distance = 0; - m_original_geom_type = Geometry.GeometryType.Unknown; - m_abs_distance_reversed = 0; - m_abs_distance = 0; - m_densify_dist = -1; - m_dA = -1; - m_b_output_loops = true; - m_bfilter = true; - } - private Geometry buffer_() { int gt = m_geometry.getType().value(); if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat @@ -485,13 +553,15 @@ private Geometry bufferPolyline_() { } assert (m_distance > 0); - m_geometry = preparePolyline_((Polyline) (m_geometry)); - - GeometryCursorForPolyline cursor = new GeometryCursorForPolyline(this, - m_bfilter); - GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Union)).execute( - cursor, m_spatialReference, m_progress_tracker); + Polyline poly = (Polyline)m_geometry; m_geometry = null; + + GeometryCursor glueing_cursor = new GlueingCursorForPolyline(poly);//glues paths together if they connect at one point + poly = null; + GeometryCursor generalized_paths = OperatorGeneralize.local().execute(glueing_cursor, m_densify_dist * 0.25, false, m_progress_tracker); + GeometryCursor simple_paths = OperatorSimplifyOGC.local().execute(generalized_paths, null, true, m_progress_tracker);//make a planar graph. + generalized_paths = null; + GeometryCursor path_buffering_cursor = new GeometryCursorForPolyline(this, simple_paths, m_bfilter); simple_paths = null; + GeometryCursor union_cursor = OperatorUnion.local().execute(path_buffering_cursor, m_spatialReference, m_progress_tracker);//(int)Operator_union::Options::enum_disable_edge_dissolver Geometry result = union_cursor.next(); return result; } @@ -742,7 +812,7 @@ private Geometry bufferPoint_(Point point) { private Geometry bufferMultiPoint_() { assert (m_distance > 0); - GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint( + GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint(this, (MultiPoint) (m_geometry), m_distance, m_spatialReference, m_densify_dist, m_max_vertex_in_complete_circle, m_progress_tracker); @@ -861,8 +931,6 @@ private Polygon bufferPolylinePath_(Polyline polyline, int ipath, } Polyline result_polyline = new Polyline(polyline.getDescription()); - // result_polyline.reserve((m_circle_template.size() / 10 + 4) * - // mp_impl.getPathSize(ipath)); MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); @@ -877,8 +945,6 @@ private Polygon bufferPolylinePath_(Polyline polyline, int ipath, (MultiPathImpl) input_multi_path._getImpl(), ipath, 0, input_multi_path.getSegmentCount(ipath), false); bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_prepare.txt", - // *result_polyline, nullptr); } return bufferCleanup_(result_polyline, false); @@ -902,7 +968,9 @@ private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { return resultPolygon; } - private int calcN_(int minN) { + private int calcN_() { + //this method should be called only once m_circle_template_size is set then; + final int minN = 4; if (m_densify_dist == 0) return m_max_vertex_in_complete_circle; @@ -998,7 +1066,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, ipath, true); edit_shape.filterClosePoints(m_filter_tolerance, false, false); if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. - // Wither bail out or + // Either bail out or // produce a circle. if (dir < 0) return 1;// negative direction produces nothing. @@ -1023,7 +1091,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, if (bfilter) { // try removing the noise that does not contribute to the buffer. - int res_filter = filterPath_(edit_shape, geom, dir, true); + int res_filter = filterPath_(edit_shape, geom, dir, true, m_abs_distance, m_filter_tolerance, m_densify_dist); assert (res_filter == 1); // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", // *edit_shape.get_geometry(geom), nullptr); @@ -1216,228 +1284,484 @@ private int cleanupBufferCommands_() { return istart; } - private boolean isGap_(Point2D pt_before, Point2D pt_current, - Point2D pt_after) { - Point2D v_gap = new Point2D(); - v_gap.sub(pt_after, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = m_abs_distance * m_abs_distance - gap_length - * gap_length * 0.25; - if (sqr_delta > 0) { - double delta = Math.sqrt(sqr_delta); - v_gap.normalize(); - v_gap.rightPerpendicular(); - Point2D p = new Point2D(); - p.sub(pt_current, pt_before); - double d = p.dotProduct(v_gap); - if (d + delta >= m_abs_distance) { - return true; + private static void protectExtremeVertices_(EditShape edit_shape, + int protection_index, int geom, int path) { + // detect very narrow corners and preserve them. We cannot reliably + // delete these. + int vprev = -1; + Point2D pt_prev = new Point2D(); + pt_prev.setNaN(); + Point2D pt = new Point2D(); + pt.setNaN(); + Point2D v_before = new Point2D(); + v_before.setNaN(); + Point2D pt_next = new Point2D(); + Point2D v_after = new Point2D(); + for (int i = 0, n = edit_shape.getPathSize(path), v = edit_shape + .getFirstVertex(path); i < n; ++i) { + if (vprev == -1) { + edit_shape.getXY(v, pt); + + vprev = edit_shape.getPrevVertex(v); + if (vprev != -1) { + edit_shape.getXY(vprev, pt_prev); + v_before.sub(pt, pt_prev); + v_before.normalize(); + } } - } - return false; + int vnext = edit_shape.getNextVertex(v); + if (vnext == -1) + break; + + edit_shape.getXY(vnext, pt_next); + v_after.sub(pt_next, pt); + v_after.normalize(); + + if (vprev != -1) { + double d = v_after.dotProduct(v_before); + if (d < -0.99 + && Math.abs(v_after.crossProduct(v_before)) < 1e-7) { + edit_shape.setUserIndex(v, protection_index, 1); + } + } + + vprev = v; + v = vnext; + pt_prev.setCoords(pt); + pt.setCoords(pt_next); + v_before.setCoords(v_after); + } } + + static private int filterPath_(EditShape edit_shape, int geom, int dir, + boolean closed, double abs_distance, double filter_tolerance, + double densify_distance) { + int path = edit_shape.getFirstPath(geom); - private int filterPath_(EditShape edit_shape, int geom, int dir, - boolean closed) { - // **********************!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // return 1; - - boolean bConvex = true; - for (int pass = 0; pass < 1; pass++) { - boolean b_filtered = false; - int ipath = edit_shape.getFirstPath(geom); - int isize = edit_shape.getPathSize(ipath); - if (isize == 0) - return 0; + int concave_index = -1; + int fixed_vertices_index = edit_shape.createUserIndex(); + protectExtremeVertices_(edit_shape, fixed_vertices_index, geom, path); + + for (int iter = 0; iter < 100; ++iter) { + int isize = edit_shape.getPathSize(path); + if (isize == 0) { + edit_shape.removeUserIndex(fixed_vertices_index); + ; + return 1; + } - int ncount = isize; - if (isize < 3) + int ivert = edit_shape.getFirstVertex(path); + int nvertices = edit_shape.getPathSize(path); + if (nvertices < 3) { + edit_shape.removeUserIndex(fixed_vertices_index); + ; return 1; + } - if (closed && !edit_shape.isClosedPath(ipath))// the path is closed + if (closed && !edit_shape.isClosedPath(path))// the path is closed // only virtually { - ncount = isize - 1; + nvertices -= 1; } - assert (dir == 1 || dir == -1); - int ivert = edit_shape.getFirstVertex(ipath); - if (!closed) - edit_shape.getNextVertex(ivert); + double abs_d = abs_distance; + final int nfilter = 64; + boolean filtered = false; + int filtered_in_pass = 0; + boolean go_back = false; + for (int i = 0; i < nvertices && ivert != -1; i++) { + int filtered_now = 0; + int v = ivert; // filter == 0 + for (int filter = 1, n = (int) Math.min(nfilter, nvertices - i); filter < n; filter++) { + v = edit_shape.getNextVertex(v, dir); + if (filter > 1) { + int num = clipFilter_(edit_shape, + fixed_vertices_index, ivert, v, dir, + abs_distance, densify_distance, nfilter); + if (num == -1) + break; - int iprev = dir > 0 ? edit_shape.getPrevVertex(ivert) : edit_shape - .getNextVertex(ivert); - int inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - int ibefore = iprev; - boolean reload = true; - Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_before_before = new Point2D(), pt_middle = new Point2D(), pt_gap_last = new Point2D( - 0, 0); - Point2D v_after = new Point2D(), v_before = new Point2D(), v_gap = new Point2D(); - Point2D temp = new Point2D(); - double abs_d = m_abs_distance; - // When the path is open we cannot process the first and the last - // vertices, so we process size - 2. - // When the path is closed, we can process all vertices. - int iter_count = closed ? ncount : isize - 2; - int gap_counter = 0; - for (int iter = 0; iter < iter_count;) { - edit_shape.getXY(inext, pt_after); - - if (reload) { - edit_shape.getXY(ivert, pt_current); - edit_shape.getXY(iprev, pt_before); - ibefore = iprev; + filtered |= num > 0; + filtered_now += num; + nvertices -= num; + } } - v_before.sub(pt_current, pt_before); - v_before.normalize(); + filtered_in_pass += filtered_now; - v_after.sub(pt_after, pt_current); - v_after.normalize(); + go_back = filtered_now > 0; - if (ibefore == inext) { + if (go_back) { + int prev = edit_shape.getPrevVertex(ivert, dir); + if (prev != -1) { + ivert = prev; + nvertices++; + continue; + } + } + + ivert = edit_shape.getNextVertex(ivert, dir); + } + + if (filtered_in_pass == 0) + break; + } + + edit_shape.removeUserIndex(fixed_vertices_index); + edit_shape.filterClosePoints(filter_tolerance, false, false); + + return 1; + } + + // This function clips out segments connecting from_vertiex to to_vertiex if + // they do not contribute to the buffer. + private static int clipFilter_(EditShape edit_shape, + int fixed_vertices_index, int from_vertex, int to_vertex, int dir, + double abs_distance, double densify_distance, final int max_filter) { + // Note: vertices marked with fixed_vertices_index cannot be deleted. + + Point2D pt1 = edit_shape.getXY(from_vertex); + Point2D pt2 = edit_shape.getXY(to_vertex); + if (pt1.equals(pt2)) + return -1; + + double densify_distance_delta = densify_distance * 0.25;// distance by + // which we can + // move the + // point closer + // to the chord + // (introducing + // an error into + // the buffer). + double erase_distance_delta = densify_distance * 0.25;// distance when + // we can erase + // the point + // (introducing + // an error into + // the buffer). + // This function goal is to modify or remove vertices between + // from_vertex and to_vertex in such a way that the result would not + // affect buffer to the left of the + // chain. + Point2D v_gap = new Point2D(); + v_gap.sub(pt2, pt1); + double gap_length = v_gap.length(); + double h2_4 = gap_length * gap_length * 0.25; + double sqr_center_to_chord = abs_distance * abs_distance - h2_4; // squared + // distance + // from + // the + // chord + // to + // the + // circle + // center + if (sqr_center_to_chord <= h2_4) + return -1;// center to chord distance is less than half gap, that + // means the gap is too wide for useful filtering (maybe + // this). + + double center_to_chord = Math.sqrt(sqr_center_to_chord); // distance + // from + // circle + // center to + // the + // chord. + + v_gap.normalize(); + Point2D v_gap_norm = new Point2D(v_gap); + v_gap_norm.rightPerpendicular(); + double chord_to_corner = h2_4 / center_to_chord; // cos(a) = + // center_to_chord / + // distance; + // chord_to_corner = + // distance / cos(a) + // - + // center_to_chord; + boolean can_erase_corner_point = chord_to_corner <= erase_distance_delta; + Point2D chord_midpoint = new Point2D(); + MathUtils.lerp(pt2, pt1, 0.5, chord_midpoint); + Point2D corner = new Point2D(v_gap_norm); + double corrected_chord_to_corner = chord_to_corner + - densify_distance_delta;// using slightly smaller than needed + // distance let us filter more. + corner.scaleAdd(Math.max(0.0, corrected_chord_to_corner), + chord_midpoint); + // corner = (p1 + p2) * 0.5 + v_gap_norm * chord_to_corner; + + Point2D center = new Point2D(v_gap_norm); + center.negate(); + center.scaleAdd(center_to_chord, chord_midpoint); + + double allowed_distance = abs_distance - erase_distance_delta; + double sqr_allowed_distance = MathUtils.sqr(allowed_distance); + double sqr_large_distance = sqr_allowed_distance * (1.9 * 1.9); + + Point2D co_p1 = new Point2D(); + co_p1.sub(corner, pt1); + Point2D co_p2 = new Point2D(); + co_p2.sub(corner, pt2); + + boolean large_distance = false;// set to true when distance + int cnt = 0; + char[] locations = new char[64]; + { + // check all vertices in the gap verifying that the gap can be + // clipped. + // + + Point2D pt = new Point2D(); + // firstly remove any duplicate vertices in the end. + for (int v = edit_shape.getPrevVertex(to_vertex, dir); v != from_vertex;) { + if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) + return -1;// this range contains protected vertex + + edit_shape.getXY(v, pt); + if (pt.equals(pt2)) { + int v1 = edit_shape.getPrevVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + continue; + } else { break; } + } - double cross = v_before.crossProduct(v_after); - double dot = v_before.dotProduct(v_after); - boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); - boolean b_write = true; - if (!bDoJoin) { - if (isGap_(pt_before, pt_current, pt_after)) { - pt_gap_last.setCoords(pt_after); - b_write = false; - ++gap_counter; - b_filtered = true; - } + Point2D prev_prev_pt = new Point2D(); + prev_prev_pt.setNaN(); + Point2D prev_pt = new Point2D(); + prev_pt.setCoords(pt1); + locations[cnt++] = 1; + int prev_v = from_vertex; + Point2D dummyPt = new Point2D(); + for (int v = edit_shape.getNextVertex(from_vertex, dir); v != to_vertex;) { + if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) + return -1;// this range contains protected vertex + + edit_shape.getXY(v, pt); + if (pt.equals(prev_pt)) { + int v1 = edit_shape.getNextVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + continue; + } - bConvex = false; + locations[cnt++] = 0; + + Point2D v1 = new Point2D(); + v1.sub(pt, pt1); + if (v1.dotProduct(v_gap_norm) < 0)// we are crossing on the + // wrong site of the chord. + // Just bail out earlier. + // Maybe we could continue + // clipping though here, but + // it seems to be + // unnecessary complicated. + return 0; + + if (Point2D.sqrDistance(pt, pt1) > sqr_large_distance + || Point2D.sqrDistance(pt, pt2) > sqr_large_distance) + large_distance = true; // too far from points, may + // contribute to the outline (in + // case of a large loop) + + char next_location = 0; + + dummyPt.sub(pt, pt1); + double cs1 = dummyPt.crossProduct(co_p1); + if (cs1 >= 0) { + next_location = 1; } - if (b_write) { - if (gap_counter > 0) { - for (;;) {// re-test back - int ibefore_before = dir > 0 ? edit_shape - .getPrevVertex(ibefore) : edit_shape - .getNextVertex(ibefore); - if (ibefore_before == ivert) - break; - - edit_shape.getXY(ibefore_before, pt_before_before); - if (isGap_(pt_before_before, pt_before, pt_gap_last)) { - pt_before.setCoords(pt_before_before); - ibefore = ibefore_before; - b_write = false; - ++gap_counter; - continue; - } else { - if (ibefore_before != inext - && isGap_(pt_before_before, pt_before, - pt_after) - && isGap_(pt_before_before, pt_current, - pt_after)) {// now the current - // point is a part - // of the gap also. - // We retest it. - pt_before.setCoords(pt_before_before); - ibefore = ibefore_before; - b_write = false; - ++gap_counter; - } - } - break; - } - } + dummyPt.sub(pt, pt2); + double cs2 = dummyPt.crossProduct(co_p2); + if (cs2 <= 0) { + next_location |= 2; + } - if (!b_write) - continue;// retest forward - - if (gap_counter > 0) { - // remove all but one gap vertices. - int p = dir > 0 ? edit_shape.getPrevVertex(iprev) - : edit_shape.getNextVertex(iprev); - for (int i = 1; i < gap_counter; i++) { - int pp = dir > 0 ? edit_shape.getPrevVertex(p) - : edit_shape.getNextVertex(p); - edit_shape.removeVertex(p, true); - p = pp; - } + if (next_location == 0) + return 0; - v_gap.sub(pt_current, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = abs_d * abs_d - gap_length - * gap_length * 0.25; - double delta = Math.sqrt(sqr_delta); - if (abs_d - delta > m_densify_dist * 0.5) { - pt_middle.add(pt_before, pt_current); - pt_middle.scale(0.5); - v_gap.normalize(); - v_gap.rightPerpendicular(); - temp.setCoords(v_gap); - temp.scale(abs_d - delta); - pt_middle.add(temp); - edit_shape.setXY(iprev, pt_middle); - } else { - // the gap is too short to be considered. Can close - // it with the straight segment; - edit_shape.removeVertex(iprev, true); - } + locations[cnt - 1] = next_location; + prev_prev_pt.setCoords(prev_pt); + prev_pt.setCoords(pt); + prev_v = v; + v = edit_shape.getNextVertex(v, dir); + } - gap_counter = 0; - } + if (cnt == 1) + return 0; - pt_before.setCoords(pt_current); - ibefore = ivert; - } + assert (!pt2.equals(prev_pt)); + locations[cnt++] = 2; + } - pt_current.setCoords(pt_after); - iprev = ivert; - ivert = inext; - // reload = false; - inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - iter++; - reload = false; + boolean can_clip_all = true; + // we can remove all points and replace them with a single corner point + // if we are moving from location 1 via location 3 to location 2 + for (int i = 1, k = 0; i < cnt; i++) { + if (locations[i] != locations[i - 1]) { + k++; + can_clip_all = k < 3 + && ((k == 1 && locations[i] == 3) || (k == 2 && locations[i] == 2)); + if (!can_clip_all) + return 0; + } + } + + if (cnt > 2 && can_clip_all && (cnt == 3 || !large_distance)) { + int clip_count = 0; + int v = edit_shape.getNextVertex(from_vertex, dir); + if (!can_erase_corner_point) { + edit_shape.setXY(v, corner); + v = edit_shape.getNextVertex(v, dir); + } + + // we can remove all vertices between from and to, because they + // don't contribute + while (v != to_vertex) { + int v1 = edit_shape.getNextVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + ++clip_count; } - if (gap_counter > 0) { - int p = dir > 0 ? edit_shape.getPrevVertex(iprev) : edit_shape - .getNextVertex(iprev); - for (int i = 1; i < gap_counter; i++) { - int pp = dir > 0 ? edit_shape.getPrevVertex(p) : edit_shape - .getNextVertex(p); - edit_shape.removeVertex(p, true); - p = pp; + return clip_count; + } + + if (cnt == 3) { + boolean case1 = (locations[0] == 1 && locations[1] == 2 && locations[2] == 2); + boolean case2 = (locations[0] == 1 && locations[1] == 1 && locations[2] == 2); + if (case1 || case2) { + // special case, when we cannot clip, but we can move the point + Point2D p1 = edit_shape.getXY(from_vertex); + int v = edit_shape.getNextVertex(from_vertex, dir); + Point2D p2 = edit_shape.getXY(v); + Point2D p3 = edit_shape.getXY(edit_shape.getNextVertex(v, dir)); + if (case2) { + Point2D temp = p1; + p1 = p3; + p3 = temp; } - pt_middle.add(pt_before, pt_current); - pt_middle.scale(0.5); - - v_gap.sub(pt_current, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = abs_d * abs_d - gap_length * gap_length - * 0.25; - assert (sqr_delta > 0); - double delta = Math.sqrt(sqr_delta); - v_gap.normalize(); - v_gap.rightPerpendicular(); - temp.setCoords(v_gap); - temp.scale(abs_d - delta); - pt_middle.add(temp); - edit_shape.setXY(iprev, pt_middle); + Point2D vec = new Point2D(); + vec.sub(p1, p2); + p3.sub(p2); + double veclen = vec.length(); + double w = p3.length(); + double wcosa = vec.dotProduct(p3) / veclen; + double wsina = Math.abs(p3.crossProduct(vec) / veclen); + double z = 2 * abs_distance - wsina; + if (z < 0) + return 0; + + double x = wcosa + Math.sqrt(wsina * z); + if (x > veclen) + return 0; + + Point2D hvec = new Point2D(); + hvec.scaleAdd(-x / veclen, vec, p3); // hvec = p3 - vec * (x / + // veclen); + double h = hvec.length(); + double y = -(h * h * veclen) / (2 * hvec.dotProduct(vec)); + + double t = (x - y) / veclen; + MathUtils.lerp(p2, p1, t, p2); + edit_shape.setXY(v, p2); + return 0; + } + } + + if (large_distance && cnt > 3) { + // we are processing more than 3 points and there are some points + // further than the + return 0; + } + + int v_prev = -1; + Point2D pt_prev = new Point2D(); + int v_cur = from_vertex; + Point2D pt_cur = new Point2D(pt1); + int cur_location = 1; + int prev_location = -1; // 1 - semiplane to the right of [f,c]. 3 - + // semiplane to the right of [c,t], 2 - both + // above fc and ct, 0 - cannot clip, -1 - + // unknown + int v_next = v_cur; + int clip_count = 0; + cnt = 1; + while (v_next != to_vertex) { + v_next = edit_shape.getNextVertex(v_next, dir); + int next_location = locations[cnt++]; + if (next_location == 0) { + if (v_next == to_vertex) + break; + + continue; } - edit_shape.filterClosePoints(m_filter_tolerance, false, false); + Point2D pt_next = edit_shape.getXY(v_next); + + if (prev_location != -1) { + int common_location = (prev_location & cur_location & next_location); + if ((common_location & 3) != 0) { + // prev and next are on the same semiplane as the current we + // can safely remove the current point. + edit_shape.removeVertex(v_cur, true); + clip_count++;// do not change prev point. + v_cur = v_next; + pt_cur.setCoords(pt_next); + cur_location = next_location; + continue; + } - if (!b_filtered) - break; + if (cur_location == 3 && prev_location != 0 + && next_location != 0) { + assert ((prev_location & next_location) == 0);// going from + // one semi + // plane to + // another + // via the + // mid. + pt_cur.setCoords(corner); + if (can_erase_corner_point || pt_cur.equals(pt_prev)) {// this + // point + // can + // be + // removed + edit_shape.removeVertex(v_cur, true); + clip_count++;// do not change prev point. + v_cur = v_next; + pt_cur.setCoords(pt_next); + cur_location = next_location; + continue; + } else { + edit_shape.setXY(v_cur, pt_cur); // snap to the corner + } + } else { + if (next_location == 0 + && cur_location != 0 + || next_location != 0 + && cur_location == 0 + || ((next_location | cur_location) == 3 + && next_location != 3 && cur_location != 3)) { + // clip + } + } + } + + prev_location = cur_location; + v_prev = v_cur; + pt_prev.setCoords(pt_cur); + v_cur = v_next; + cur_location = next_location; + pt_cur.setCoords(pt_next); } - return 1; + return clip_count; } - + private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { if (mp_impl.getPathSize(ipath) == 1) return true; @@ -1510,7 +1834,7 @@ private void addCircle_(MultiPathImpl result_mp, Point point) { // avoid unnecessary memory allocation for the circle template. Just do // the point here. - int N = calcN_(4); + int N = m_circle_template_size; int real_size = (N + 3) / 4; double dA = (Math.PI * 0.5) / real_size; // result_mp.reserve(real_size * 4); @@ -1590,5 +1914,4 @@ private Polygon setStrongSimple_(Polygon poly) { ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); return poly; } - } diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index bafe51e0..ab4b89c9 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -25,8 +25,7 @@ class ConvexHull { /* - * Constructor for a Convex_hull object.Used for dynamic insertion of - * geometries to create a convex hull. + * Constructor for a Convex_hull object. Used for dynamic insertion of geometries to create a convex hull. */ ConvexHull() { m_tree_hull = new Treap(); @@ -37,24 +36,23 @@ class ConvexHull { m_call_back = new CallBackShape(this); } - private ConvexHull(MultiVertexGeometry mvg) { + private ConvexHull(AttributeStreamOfDbl stream, int n) { m_tree_hull = new Treap(); - m_tree_hull.setCapacity(20); - m_mvg = mvg; - m_call_back = new CallBackMvg(this); + m_tree_hull.setCapacity(Math.min(20, n)); + m_stream = stream; + m_call_back = new CallBackStream(this); } - private ConvexHull(Point2D[] points) { + private ConvexHull(Point2D[] points, int n) { m_tree_hull = new Treap(); - m_tree_hull.setCapacity(20); + m_tree_hull.setCapacity(Math.min(20, n)); m_points = points; m_call_back = new CallBackPoints(this); } /** - * Adds a geometry to the current bounding geometry using an incremental - * algorithm for dynamic insertion. \param geometry The geometry to add to - * the bounding geometry. + * Adds a geometry to the current bounding geometry using an incremental algorithm for dynamic insertion. + * \param geometry The geometry to add to the bounding geometry. */ void addGeometry(Geometry geometry) { @@ -73,20 +71,19 @@ else if (type == Geometry.GeometryType.Point) } /** - * Gets the current bounding geometry. Returns a Geometry. + * Gets the current bounding geometry. + * Returns a Geometry. */ Geometry getBoundingGeometry() { - // Extracts the convex hull from the tree. Reading the tree in order - // from first to last is the resulting convex hull. + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. Point point = new Point(); int first = m_tree_hull.getFirst(-1); Polygon hull = new Polygon(m_shape.getVertexDescription()); m_shape.queryPoint(m_tree_hull.getElement(first), point); hull.startPath(point); - for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull - .getNext(i)) { + for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull.getNext(i)) { m_shape.queryPoint(m_tree_hull.getElement(i), point); hull.lineTo(point); } @@ -96,58 +93,110 @@ Geometry getBoundingGeometry() { /** * Static method to construct the convex hull of a Multi_vertex_geometry. - * Returns a Polygon. \param mvg The geometry used to create the convex - * hull. + * Returns a Geometry. + * \param mvg The geometry used to create the convex hull. */ - static Polygon construct(MultiVertexGeometry mvg) { - ConvexHull convex_hull = new ConvexHull(mvg); + static Geometry construct(MultiVertexGeometry mvg) { + if (mvg.isEmpty()) + return new Polygon(mvg.getDescription()); + + MultiVertexGeometryImpl mvg_impl = (MultiVertexGeometryImpl) mvg._getImpl(); + int N = mvg_impl.getPointCount(); + + if (N <= 2) { + if (N == 1 || mvg_impl.getXY(0).equals(mvg_impl.getXY(1))) { + Point point = new Point(mvg_impl.getDescription()); + mvg_impl.getPointByVal(0, point); + return point; + } else { + Point pt = new Point(); + Polyline polyline = new Polyline(mvg_impl.getDescription()); + mvg_impl.getPointByVal(0, pt); + polyline.startPath(pt); + mvg_impl.getPointByVal(1, pt); + polyline.lineTo(pt); + return polyline; + } + } + + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) mvg_impl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); + ConvexHull convex_hull = new ConvexHull(stream, N); - int N = mvg.getPointCount(); int t0 = 0, tm = 1; Point2D pt_0 = new Point2D(); Point2D pt_m = new Point2D(); Point2D pt_p = new Point2D(); - mvg.getXY(t0, pt_0); + stream.read(t0 << 1, pt_0); while (true) { - mvg.getXY(tm, pt_m); - if (!(pt_m.isEqual(pt_0, NumberUtils.doubleEps()) && tm < N - 1)) + if (tm >= N) + break; + + stream.read(tm << 1, pt_m); + if (!pt_m.isEqual(pt_0, NumberUtils.doubleEps())) break; tm++; // We don't want to close the gap between t0 and tm. } convex_hull.m_tree_hull.addElement(t0, -1); - convex_hull.m_tree_hull.addBiggestElement(tm, -1); - for (int tp = tm + 1; tp < mvg.getPointCount(); tp++) {// Dynamically - // insert into - // the current - // convex hull + if (tm < N) { + convex_hull.m_tree_hull.addBiggestElement(tm, -1); - mvg.getXY(tp, pt_p); - int p = convex_hull.treeHull_(pt_p); + for (int tp = tm + 1; tp < mvg_impl.getPointCount(); tp++) {// Dynamically insert into the current convex hull - if (p != -1) - convex_hull.m_tree_hull.setElement(p, tp); // reset the place - // holder to the - // point index. + stream.read(tp << 1, pt_p); + int p = convex_hull.treeHull_(pt_p); + + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. + } } - // Extracts the convex hull from the tree. Reading the tree in order - // from first to last is the resulting convex hull. - Point point = new Point(); - int first = convex_hull.m_tree_hull.getFirst(-1); - Polygon hull = new Polygon(mvg.getDescription()); - mvg.getPointByVal(convex_hull.m_tree_hull.getElement(first), point); - hull.startPath(point); + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. - for (int i = convex_hull.m_tree_hull.getNext(first); i != -1; i = convex_hull.m_tree_hull - .getNext(i)) { - mvg.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); - hull.lineTo(point); + VertexDescription description = mvg_impl.getDescription(); + boolean b_has_attributes = (description.getAttributeCount() > 1); + int point_count = convex_hull.m_tree_hull.size(-1); + + Geometry hull; + + if (point_count >= 2) { + if (point_count >= 3) + hull = new Polygon(description); + else + hull = new Polyline(description); + + MultiPathImpl hull_impl = (MultiPathImpl) hull._getImpl(); + hull_impl.addPath((Point2D[]) null, 0, true); + + Point point = null; + if (b_has_attributes) + point = new Point(); + + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) { + if (b_has_attributes) { + mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); + hull_impl.insertPoint(0, -1, point); + } else { + stream.read(convex_hull.m_tree_hull.getElement(i) << 1, pt_p); + hull_impl.insertPoint(0, -1, pt_p); + } + } + } else { + assert (point_count == 1); + + if (b_has_attributes) { + Point point = new Point(description); + mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)), point); + hull = point; + } else { + stream.read(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)) << 1, pt_p); + hull = new Point(pt_p); + } } return hull; @@ -156,65 +205,57 @@ static Polygon construct(MultiVertexGeometry mvg) { /** * Static method to construct the convex hull from an array of points. The * out_convex_hull array will be populated with the subset of index - * positions which contribute to the convex hull. Returns the number of - * points in the convex hull. \param points The points used to create the - * convex hull. \param count The number of points in the input Point2D - * array. \param out_convex_hull An index array allocated by the user at - * least as big as the size of the input points array. + * positions which contribute to the convex hull. + * Returns the number of points in the convex hull. + * \param points The points used to create the convex hull. + * \param count The number of points in the input Point2D array. + * \param out_convex_hull An index array allocated by the user at least as big as the size of the input points array. */ - static int construct(Point2D[] points, int count, int[] bounding_geometry) { - ConvexHull convex_hull = new ConvexHull(points); + static int construct(Point2D[] points, int count, int[] out_convex_hull) { + ConvexHull convex_hull = new ConvexHull(points, count); int t0 = 0, tm = 1; Point2D pt_0 = points[t0]; - while (points[tm].isEqual(pt_0, NumberUtils.doubleEps()) - && tm < count - 1) + while (tm < count && points[tm].isEqual(pt_0, NumberUtils.doubleEps())) tm++; // We don't want to close the gap between t0 and tm. convex_hull.m_tree_hull.addElement(t0, -1); - convex_hull.m_tree_hull.addBiggestElement(tm, -1); - for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the - // current convex hull. + if (tm < count) { + convex_hull.m_tree_hull.addBiggestElement(tm, -1); + + for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the current convex hull. - Point2D pt_p = points[tp]; - int p = convex_hull.treeHull_(pt_p); + Point2D pt_p = points[tp]; + int p = convex_hull.treeHull_(pt_p); - if (p != -1) - convex_hull.m_tree_hull.setElement(p, tp); // reset the place - // holder to the - // point index. + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. + } } - // Extracts the convex hull from the tree. Reading the tree in order - // from first to last is the resulting convex hull. + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. int out_count = 0; - for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull - .getNext(i)) - bounding_geometry[out_count++] = convex_hull.m_tree_hull - .getElement(i); + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) + out_convex_hull[out_count++] = convex_hull.m_tree_hull.getElement(i); return out_count; } /** - * Returns true if the given path of the input MultiPath is convex. Returns - * false otherwise. \param multi_path The MultiPath to check if the path is - * convex. \param path_index The path of the MultiPath to check if its - * convex. + * Returns true if the given path of the input MultiPath is convex. Returns false otherwise. + * \param multi_path The MultiPath to check if the path is convex. + * \param path_index The path of the MultiPath to check if its convex. */ - static boolean isPathConvex(MultiPath multi_path, int path_index, - ProgressTracker progress_tracker) { + static boolean isPathConvex(MultiPath multi_path, int path_index, ProgressTracker progress_tracker) { MultiPathImpl mimpl = (MultiPathImpl) multi_path._getImpl(); int path_start = mimpl.getPathStart(path_index); int path_end = mimpl.getPathEnd(path_index); - boolean bxyclosed = !mimpl.isClosedPath(path_index) - && mimpl.isClosedPathInXYPlane(path_index); + boolean bxyclosed = !mimpl.isClosedPath(path_index) && mimpl.isClosedPathInXYPlane(path_index); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION)); int position_start = 2 * path_start; int position_end = 2 * path_end; @@ -224,23 +265,15 @@ static boolean isPathConvex(MultiPath multi_path, int path_index, if (position_end - position_start < 6) return true; - // This matches the logic for case 1 of the tree hull algorithm. The - // idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and - // we see if - // a new point (pt_pivot) is among the transitive tournament for pt_0, - // knowing that pt_pivot comes after pt_m. + // This matches the logic for case 1 of the tree hull algorithm. The idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and we see if + // a new point (pt_pivot) is among the transitive tournament for pt_0, knowing that pt_pivot comes after pt_m. // We check three conditions: - // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is - // convex) - // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is - // convex) (pt_1 is the next point after pt_0) - // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards - // is convex) (pt_m_prev is the previous point before pt_m) + // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is convex) + // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is convex) (pt_1 is the next point after pt_0) + // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards is convex) (pt_m_prev is the previous point before pt_m) - // If all three of the above conditions are clockwise, then pt_pivot is - // among the transitive tournament for pt_0, and therefore the polygon - // pt_0, ..., pt_m, pt_pivot is convex. + // If all three of the above conditions are clockwise, then pt_pivot is among the transitive tournament for pt_0, and therefore the polygon pt_0, ..., pt_m, pt_pivot is convex. Point2D pt_0 = new Point2D(), pt_m = new Point2D(), pt_pivot = new Point2D(); position.read(position_start, pt_0); @@ -256,8 +289,7 @@ static boolean isPathConvex(MultiPath multi_path, int path_index, Point2D pt_1 = new Point2D(pt_m.x, pt_m.y); Point2D pt_m_prev = new Point2D(); - // Assume that pt_0,...,pt_m is convex. Check if the next point, - // pt_pivot, maintains the convex invariant. + // Assume that pt_0,...,pt_m is convex. Check if the next point, pt_pivot, maintains the convex invariant. for (int i = position_start + 6; i < position_end; i += 2) { pt_m_prev.setCoords(pt_m); pt_m.setCoords(pt_pivot); @@ -325,7 +357,7 @@ private void addSegment_(Segment segment) { segment.queryStart(point); int t_start = m_shape.addPoint(m_path_handle, point); m_tree_hull.setElement(p_start, t_start); // reset the place holder - // to tp + // to tp } Point2D pt_end = segment.getEndXY(); @@ -335,7 +367,7 @@ private void addSegment_(Segment segment) { segment.queryEnd(point); int t_end = m_shape.addPoint(m_path_handle, point); m_tree_hull.setElement(p_end, t_end); // reset the place holder to - // tp + // tp } } @@ -353,9 +385,7 @@ private int addPoint_(Point2D pt_p) { int p = -1; if (m_tree_hull.size(-1) == 0) { - p = m_tree_hull.addElement(-4, -1); // set place holder to -4 to - // indicate the first element - // being added (t0). + p = m_tree_hull.addElement(-4, -1); // reset the place holder to tp return p; } @@ -363,15 +393,8 @@ private int addPoint_(Point2D pt_p) { int t0 = m_tree_hull.getElement(m_tree_hull.getFirst(-1)); Point2D pt_0 = m_shape.getXY(t0); - if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want - // to close the - // gap between - // t0 and tm. - p = m_tree_hull.addBiggestElement(-5, -1); // set place holder - // to -5 to indicate - // the second - // element being - // added (tm). + if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want to close the gap between t0 and tm. + p = m_tree_hull.addBiggestElement(-5, -1); // set place holder to -5 to indicate the second element being added (tm). return p; } @@ -380,8 +403,7 @@ private int addPoint_(Point2D pt_p) { return p; } - // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in - // Computer Science 606, page 47. + // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in Computer Science 606, page 47. private int treeHull_(Point2D pt_pivot) { assert (m_tree_hull.size(-1) >= 2); @@ -398,55 +420,31 @@ private int treeHull_(Point2D pt_pivot) { m_call_back.getXY(t0, pt_0); m_call_back.getXY(tm, pt_m); - assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert - // that the - // gap is - // not - // closed + assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert that the gap is not closed - int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines - // case - // 1, - // 2, - // 3 + int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines case 1, 2, 3 if (isClockwise_(orient_m_p_0)) {// Case 1: tp->t0->tm is clockwise - p = m_tree_hull.addBiggestElement(-1, -1); // set place holder - // to -1 for case 1. + p = m_tree_hull.addBiggestElement(-1, -1); // set place holder to -1 for case 1. int l = treeHullWalkBackward_(pt_pivot, last, first); if (l != first) - treeHullWalkForward_(pt_pivot, first, - m_tree_hull.getPrev(l)); + treeHullWalkForward_(pt_pivot, first, m_tree_hull.getPrev(l)); continue; } - if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is - // clockwise - int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull - .getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; + if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is clockwise + int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull.getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; int tk, tk_prev; Point2D pt_k = new Point2D(); Point2D pt_k_prev = new Point2D(); - while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to - // find k such - // that - // t0->tp->tj - // holds (i.e. - // clockwise) - // for j >= k. - // Hence, - // tj->tp->t0 is - // clockwise (or - // degenerate) - // for j < k. + while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to find k such that t0->tp->tj holds (i.e. clockwise) for j >= k. Hence, tj->tp->t0 is clockwise (or degenerate) for j < k. tk = m_tree_hull.getElement(k); m_call_back.getXY(tk, pt_k); - int orient_k_p_0 = Point2D.orientationRobust(pt_k, - pt_pivot, pt_0); + int orient_k_p_0 = Point2D.orientationRobust(pt_k, pt_pivot, pt_0); if (isCounterClockwise_(orient_k_p_0)) { k_max = k; @@ -463,23 +461,17 @@ private int treeHull_(Point2D pt_pivot) { tk_prev = m_tree_hull.getElement(k_prev); m_call_back.getXY(tk, pt_k); m_call_back.getXY(tk_prev, pt_k_prev); - assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, - pt_pivot, pt_0)) && !isCounterClockwise_(Point2D - .orientationRobust(pt_k_prev, pt_pivot, pt_0))); - assert (k_prev != first || isCounterClockwise_(Point2D - .orientationRobust(pt_k, pt_pivot, pt_0))); + assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0)) && !isCounterClockwise_(Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_0))); + assert (k_prev != first || isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0))); if (k_prev != first) { - int orient_k_prev_p_k = Point2D.orientationRobust( - pt_k_prev, pt_pivot, pt_k); + int orient_k_prev_p_k = Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_k); if (!isClockwise_(orient_k_prev_p_k)) - continue; // pt_pivot is inside the hull (or on the - // boundary) + continue; // pt_pivot is inside the hull (or on the boundary) } - p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, - false, -1); // set place holder to -2 for case 2. + p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, false, -1); // set place holder to -2 for case 2. treeHullWalkForward_(pt_pivot, k, last); treeHullWalkBackward_(pt_pivot, k_prev, first); @@ -488,40 +480,17 @@ private int treeHull_(Point2D pt_pivot) { assert (isDegenerate_(orient_m_p_0)); {// Case 3: degenerate + int between = isBetween_(pt_pivot, pt_m, pt_0); - if (m_line == null) - m_line = new Line(); - - m_line.setStartXY(pt_m); - m_line.setEndXY(pt_0); - - double scalar = m_line.getClosestCoordinate(pt_pivot, true); // if - // scalar - // is - // between - // 0 - // and - // 1, - // then - // we - // don't - // need - // to - // add - // tp. - - if (scalar < 0.0) { + if (between == -1) { int l = m_tree_hull.getPrev(last); m_tree_hull.deleteNode(last, -1); - p = m_tree_hull.addBiggestElement(-3, -1); // set place - // holder to -3 - // for case 3. + p = m_tree_hull.addBiggestElement(-3, -1); // set place holder to -3 for case 3. treeHullWalkBackward_(pt_pivot, l, first); - } else if (scalar > 1.0) { + } else if (between == 1) { int j = m_tree_hull.getNext(first); m_tree_hull.deleteNode(first, -1); - p = m_tree_hull.addElementAtPosition(-1, j, -3, true, - false, -1); // set place holder to -3 for case 3. + p = m_tree_hull.addElementAtPosition(-1, j, -3, true, false, -1); // set place holder to -3 for case 3. treeHullWalkForward_(pt_pivot, j, last); } @@ -545,19 +514,11 @@ private int treeHullWalkForward_(Point2D pt_pivot, int start, int end) { m_call_back.getXY(tj, pt_j); - while (j != end && m_tree_hull.size(-1) > 2) {// Stops when we find a - // clockwise triple - // containting the pivot - // point, or when the - // tree_hull size is 2. - // Deletes non-clockwise - // triples along the - // way. + while (j != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. int tj_next = m_tree_hull.getElement(j_next); m_call_back.getXY(tj_next, pt_j_next); - int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, - pt_pivot, pt_j); + int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, pt_pivot, pt_j); if (isClockwise_(orient_j_next_p_j)) break; @@ -585,19 +546,11 @@ private int treeHullWalkBackward_(Point2D pt_pivot, int start, int end) { m_call_back.getXY(tl, pt_l); - while (l != end && m_tree_hull.size(-1) > 2) {// Stops when we find a - // clockwise triple - // containting the pivot - // point, or when the - // tree_hull size is 2. - // Deletes non-clockwise - // triples along the - // way. + while (l != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. int tl_prev = m_tree_hull.getElement(l_prev); m_call_back.getXY(tl_prev, pt_l_prev); - int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, - pt_l_prev); + int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, pt_l_prev); if (isClockwise_(orient_l_p_l_prev)) break; @@ -661,6 +614,71 @@ private static boolean isDegenerate_(int orientation) { return orientation == 0.0; } + private static int isBetween_(Point2D pt_pivot, Point2D pt_m, Point2D pt_0) { + int ordinate = -1; + + if (pt_m.y == pt_0.y) { + ordinate = 0; + } else if (pt_m.x == pt_0.x) { + ordinate = 1; + } else {// use bigger ordinate, but shouldn't matter + + double diff_x = Math.abs(pt_m.x - pt_0.x); + double diff_y = Math.abs(pt_m.y - pt_0.y); + + if (diff_x >= diff_y) + ordinate = 0; + else + ordinate = 1; + } + + int res = -1; + + if (ordinate == 0) { + assert (pt_m.x != pt_0.x); + + if (pt_m.x < pt_0.x) { + if (pt_pivot.x < pt_m.x) + res = -1; + else if (pt_0.x < pt_pivot.x) + res = 1; + else + res = 0; + } else { + assert (pt_0.x < pt_m.x); + + if (pt_m.x < pt_pivot.x) + res = -1; + else if (pt_pivot.x < pt_0.x) + res = 1; + else + res = 0; + } + } else { + assert (pt_m.y != pt_0.y); + + if (pt_m.y < pt_0.y) { + if (pt_pivot.y < pt_m.y) + res = -1; + else if (pt_0.y < pt_pivot.y) + res = 1; + else + res = 0; + } else { + assert (pt_0.y < pt_m.y); + + if (pt_m.y < pt_pivot.y) + res = -1; + else if (pt_pivot.y < pt_0.y) + res = 1; + else + res = 0; + } + } + + return res; + } + private static abstract class CallBack { abstract void getXY(int ti, Point2D pt); @@ -687,16 +705,16 @@ void deleteNode(int i) { } } - private static final class CallBackMvg extends CallBack { + private static final class CallBackStream extends CallBack { private ConvexHull m_convex_hull; - CallBackMvg(ConvexHull convex_hull) { + CallBackStream(ConvexHull convex_hull) { m_convex_hull = convex_hull; } @Override void getXY(int ti, Point2D pt) { - m_convex_hull.m_mvg.getXY(ti, pt); + m_convex_hull.m_stream.read(ti << 1, pt); } @Override @@ -726,7 +744,7 @@ void deleteNode(int i) { // Members private Treap m_tree_hull; private EditShape m_shape; - private MultiVertexGeometry m_mvg; + private AttributeStreamOfDbl m_stream; private Point2D[] m_points; private int m_geometry_handle; private int m_path_handle; diff --git a/src/main/java/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java index c3d5c522..5bcf0460 100644 --- a/src/main/java/com/esri/core/geometry/ECoordinate.java +++ b/src/main/java/com/esri/core/geometry/ECoordinate.java @@ -27,6 +27,18 @@ class ECoordinate { private double m_value; private double m_eps; + ECoordinate() { + set(0.0, 0.0); + } + + ECoordinate(double v) { + set(v); + } + + ECoordinate(ECoordinate v) { + set(v); + } + double epsCoordinate() { return NumberUtils.doubleEps(); } @@ -160,7 +172,7 @@ void mul(double v) { } void mul(ECoordinate v_1, ECoordinate v_2) { - double r = Math.abs(v_1.m_value) * Math.abs(v_2.m_value); + double r = v_1.m_value * v_2.m_value; m_eps = v_1.m_eps * Math.abs(v_2.m_value) + v_2.m_eps * Math.abs(v_1.m_value) + v_1.m_eps * v_2.m_eps + epsCoordinate() * Math.abs(r); diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 36ce09a8..1dbdb37b 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -1865,6 +1865,14 @@ int getPrevVertex(int currentVertex) { return m_vertex_index_list.getField(currentVertex, 1); } + int getPrevVertex(int currentVertex, int dir) { + return dir > 0 ? m_vertex_index_list.getField(currentVertex, 1) : m_vertex_index_list.getField(currentVertex, 2); + } + + int getNextVertex(int currentVertex, int dir) { + return dir > 0 ? m_vertex_index_list.getField(currentVertex, 2) : m_vertex_index_list.getField(currentVertex, 1); + } + // Returns a path the vertex belongs to. int getPathFromVertex(int vertex) { return m_vertex_index_list.getField(vertex, 3); diff --git a/src/main/java/com/esri/core/geometry/EnvSrlzr.java b/src/main/java/com/esri/core/geometry/EnvSrlzr.java new file mode 100644 index 00000000..a6cea40a --- /dev/null +++ b/src/main/java/com/esri/core/geometry/EnvSrlzr.java @@ -0,0 +1,95 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for Envelope +public class EnvSrlzr implements Serializable { + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; + + public Object readResolve() throws ObjectStreamException { + Envelope env = null; + if (descriptionBitMask == -1) + return null; + + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + env = new Envelope(vd); + if (attribs != null) { + env.setCoords(attribs[0], attribs[1], attribs[2], attribs[3]); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + env.setInterval(semantics, ord, attribs[index++], attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + + return env; + } + + public void setGeometryByValue(Envelope env) throws ObjectStreamException { + try { + attribs = null; + if (env == null) { + descriptionBitMask = -1; + } + + VertexDescription vd = env.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + if (env.isEmpty()) { + return; + } + + attribs = new double[vd.getTotalComponentCount() * 2]; + attribs[0] = env.getXMin(); + attribs[1] = env.getYMin(); + attribs[2] = env.getXMax(); + attribs[3] = env.getYMax(); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + Envelope1D e = env.queryInterval(semantics, ord); + attribs[index++] = e.vmin; + attribs[index++] = e.vmax; + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 6991f26e..1370ca4f 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -32,8 +32,9 @@ /** * An envelope is an axis-aligned rectangle. */ -public final class Envelope extends Geometry implements Serializable { - private static final long serialVersionUID = 2L; +public class Envelope extends Geometry implements Serializable { + //We are using writeReplace instead. + //private static final long serialVersionUID = 2L; Envelope2D m_envelope = new Envelope2D(); @@ -58,20 +59,20 @@ public Envelope(Point center, double width, double height) { _setFromPoint(center, width, height); } - Envelope(Envelope2D env2D) { + public Envelope(Envelope2D env2D) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); m_envelope.setCoords(env2D); m_envelope.normalize(); } - Envelope(VertexDescription vd) { + public Envelope(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; m_envelope.setEmpty(); } - Envelope(VertexDescription vd, Envelope2D env2D) { + public Envelope(VertexDescription vd, Envelope2D env2D) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; @@ -215,16 +216,16 @@ public double getCenterY() { return m_envelope.getCenterY(); } - /** + /** * The x and y-coordinates of the center of the envelope. * * @return A point whose x and y-coordinates are that of the center of the envelope. */ - Point2D getCenterXY() { + public Point2D getCenterXY() { return m_envelope.getCenter(); } - void getCenter(Point point_out) { + public void getCenter(Point point_out) { point_out.assignVertexDescription(m_description); if (isEmpty()) { point_out.setEmpty(); @@ -244,7 +245,7 @@ void getCenter(Point point_out) { point_out.setXY(m_envelope.getCenter()); } - void merge(Point2D pt) { + public void merge(Point2D pt) { _touch(); m_envelope.merge(pt); } @@ -341,7 +342,7 @@ void _setFromPoint(Point centerPoint) { } } - void merge(Envelope2D other) { + public void merge(Envelope2D other) { _touch(); m_envelope.merge(other); } @@ -415,7 +416,7 @@ public void copyTo(Geometry dst) { { envDst._ensureAttributes(); System.arraycopy(m_attributes, 0, envDst.m_attributes, 0, - (m_description._getTotalComponents() - 2) * 2); + (m_description.getTotalComponentCount() - 2) * 2); } } @@ -493,7 +494,7 @@ public void setInterval(int semantics, int ordinate, Envelope1D env) { } } - void queryCoordinates(Point2D[] dst) { + public void queryCoordinates(Point2D[] dst) { if (dst == null || dst.length < 4 || m_envelope.isEmpty()) throw new IllegalArgumentException(); @@ -573,7 +574,7 @@ public void queryCornerByVal(int index, Point ptDst) { } } - void queryCorner(int index, Point2D ptDst) { + public void queryCorner(int index, Point2D ptDst) { Point2D p = m_envelope.queryCorner(index); ptDst.setCoords(p.x, p.y); } @@ -645,8 +646,8 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, void _ensureAttributes() { _touch(); - if (m_attributes == null && m_description._getTotalComponents() > 2) { - m_attributes = new double[(m_description._getTotalComponents() - 2) * 2]; + if (m_attributes == null && m_description.getTotalComponentCount() > 2) { + m_attributes = new double[(m_description.getTotalComponentCount() - 2) * 2]; int offset0 = _getEndPointOffset(m_description, 0); int offset1 = _getEndPointOffset(m_description, 1); @@ -672,10 +673,10 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { return; } - if (newDescription._getTotalComponents() > 2) { + if (newDescription.getTotalComponentCount() > 2) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; int old_offset0 = _getEndPointOffset(m_description, 0); int old_offset1 = _getEndPointOffset(m_description, 1); @@ -793,10 +794,10 @@ int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { } static int _getEndPointOffset(VertexDescription vd, int endPoint) { - return endPoint * (vd._getTotalComponents() - 2); + return endPoint * (vd.getTotalComponentCount() - 2); } - boolean isIntersecting(Envelope2D other) { + public boolean isIntersecting(Envelope2D other) { return m_envelope.isIntersecting(other); } @@ -875,7 +876,7 @@ public void normalize() {// TODO: attributes * * @return The center point of the envelope. */ - Point2D getCenter2D() { + public Point2D getCenter2D() { return m_envelope.getCenter(); } @@ -1005,7 +1006,7 @@ public boolean equals(Object _other) { if (!this.m_envelope.equals(other.m_envelope)) return false; - for (int i = 0, n = (m_description._getTotalComponents() - 2) * 2; i < n; i++) + for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) if (m_attributes[i] != other.m_attributes[i]) return false; @@ -1022,7 +1023,7 @@ public int hashCode() { int hashCode = m_description.hashCode(); hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); if (!isEmpty() && m_attributes != null) { - for (int i = 0, n = (m_description._getTotalComponents() - 2) * 2; i < n; i++) { + for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) { hashCode = NumberUtils.hash(hashCode, m_attributes[i]); } } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 9e30e08b..96540895 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -45,12 +45,20 @@ public Envelope1D(double _vmin, double _vmax) { setCoords(_vmin, _vmax); } + public Envelope1D(Envelope1D other) { + setCoords(other); + } + public void setCoords(double _vmin, double _vmax) { vmin = _vmin; vmax = _vmax; normalize(); } + public void setCoords(Envelope1D other) { + setCoords(other.vmin, other.vmax); + } + public void normalize() { if (NumberUtils.isNaN(vmin)) return; @@ -71,7 +79,7 @@ public void setEmpty() { } public boolean isEmpty() { - return NumberUtils.isNaN(vmin); + return NumberUtils.isNaN(vmin) || NumberUtils.isNaN(vmax); } public void setInfinite() { diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 3b9a02f7..172619dd 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -59,6 +59,12 @@ public static Envelope2D construct(double _xmin, double _ymin, return env; } + public static Envelope2D construct(Envelope2D other) { + Envelope2D env = new Envelope2D(); + env.setCoords(other); + return env; + } + public Envelope2D() { setEmpty(); } @@ -70,6 +76,10 @@ public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { ymax = _ymax; } + public Envelope2D(Envelope2D other) { + setCoords(other); + } + public void setCoords(double _x, double _y) { xmin = _x; ymin = _y; @@ -144,7 +154,7 @@ public void setInfinite() { } public boolean isEmpty() { - return NumberUtils.isNaN(xmin); + return NumberUtils.isNaN(xmin) || NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) || NumberUtils.isNaN(ymax); } public void setCoords(Envelope1D xinterval, Envelope1D yinterval) { @@ -240,14 +250,13 @@ public void zoom(double factorX, double factorY) { } /** - * Checks if this envelope intersects the other. + * Checks if this envelope intersects the other. * @return True if this envelope intersects the other. */ public boolean isIntersecting(Envelope2D other) { - return !isEmpty() - && !other.isEmpty() - && ((xmin <= other.xmin) ? xmax >= other.xmin - : other.xmax >= xmin) + // No need to check if empty, this will work for empty envelopes too + // (IEEE math) + return ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin); // check // that @@ -257,7 +266,7 @@ public boolean isIntersecting(Envelope2D other) { } /** - * Checks if this envelope intersects the other assuming neither one is empty. + * Checks if this envelope intersects the other assuming neither one is empty. * @return True if this envelope intersects the other. Assumes this and * other envelopes are not empty. */ @@ -271,6 +280,23 @@ public boolean isIntersectingNE(Envelope2D other) { // overlap } + /** + * Checks if this envelope intersects the other. + * @return True if this envelope intersects the other. + */ + public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double ymax_) { + // No need to check if empty, this will work for empty geoms too (IEEE + // math) + return ((xmin <= xmin_) ? xmax >= xmin_ : xmax_ >= xmin) && // check + // that x + // projections + // overlap + ((ymin <= ymin_) ? ymax >= ymin_ : ymax_ >= ymin); // check that + // y + // projections + // overlap + } + /** * Intersects this envelope with the other and stores result in this * envelope. @@ -303,15 +329,20 @@ public boolean intersect(Envelope2D other) { } /** - * Queries a corner of the envelope. - * - * @param index Indicates a corner of the envelope. - *

0 => lower left or (xmin, ymin) - *

1 => upper left or (xmin, ymax) - *

2 => upper right or (xmax, ymax) - *

3 => lower right or (xmax, ymin) - * @return Point at a corner of the envelope. - * + * Queries a corner of the envelope. + * + * @param index + * Indicates a corner of the envelope. + *

+ * 0 means lower left or (xmin, ymin) + *

+ * 1 means upper left or (xmin, ymax) + *

+ * 2 means upper right or (xmax, ymax) + *

+ * 3 means lower right or (xmax, ymin) + * @return Point at a corner of the envelope. + * */ public Point2D queryCorner(int index) { switch (index) { @@ -1081,6 +1112,62 @@ public double sqrDistance(Envelope2D other) return dx * dx + dy * dy; } + /** + * Calculates minimum squared distance from this envelope to the other. + * Returns 0 for empty envelopes. + */ + public double sqrDistance(double xmin_, double ymin_, double xmax_, double ymax_) + { + double dx = 0; + double dy = 0; + double nn; + + nn = xmin - xmax_; + if (nn > dx) + dx = nn; + + nn = ymin - ymax_; + if (nn > dy) + dy = nn; + + nn = xmin_ - xmax; + if (nn > dx) + dx = nn; + + nn = ymin_ - ymax; + if (nn > dy) + dy = nn; + + return dx * dx + dy * dy; + } + + /** + *Returns squared max distance between two bounding boxes. This is furthest distance between points on the two envelopes. + * + *@param other The bounding box to calculate the max distance two. + *@return Squared distance value. + */ + public double sqrMaxDistance(Envelope2D other) { + if (isEmpty() || other.isEmpty()) + return NumberUtils.TheNaN; + + double dist = 0; + Point2D[] points = new Point2D[4]; + queryCorners(points); + Point2D[] points_o = new Point2D[4]; + other.queryCorners(points_o); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + double d = Point2D.sqrDistance(points[i], points_o[j]); + if (d > dist) { + dist = d; + } + } + } + + return dist; + } + /** * Calculates minimum squared distance from this envelope to the point. * Returns 0 for empty envelopes. diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 98d7ad8c..35b83daa 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -43,7 +43,6 @@ void startConstruction() { m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); } else { - m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); } @@ -100,8 +99,7 @@ void endRedConstruction() { m_b_add_red = false; - if (m_envelopes_red != null && m_envelopes_red.size() > 0 - && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { if (m_function == -1) m_function = State.initializeRedBlue; else if (m_function == State.initializeBlue) @@ -142,8 +140,7 @@ void endBlueConstruction() { m_b_add_blue = false; - if (m_envelopes_red != null && m_envelopes_red.size() > 0 - && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { if (m_function == -1) m_function = State.initializeRedBlue; else if (m_function == State.initializeRed) @@ -255,6 +252,20 @@ void setTolerance(double tolerance) { m_tolerance = tolerance; } + /* + * Returns a reference to the envelope at the given handle. Use this for the red/red intersection case. + */ + Envelope2D getEnvelope(int handle) { + return m_envelopes_red.get(handle); + } + + /* + * Returns the user element associated with handle. Use this for the red/red intersection case. + */ + int getElement(int handle) { + return m_elements_red.read(handle); + } + /* * Returns a reference to the red envelope at handle_a. */ @@ -279,7 +290,6 @@ int getRedElement(int handle_a) { /* * Returns the user element associated with handle_b. */ - int getBlueElement(int handle_b) { return m_elements_blue.read(handle_b); } @@ -311,8 +321,7 @@ int getBlueElement(int handle_b) { private boolean m_b_add_red; private boolean m_b_add_blue; private boolean m_b_add_red_red; - - boolean m_b_done; + private boolean m_b_done; private static boolean isTop_(int y_end_point_handle) { return (y_end_point_handle & 0x1) == 1; @@ -345,16 +354,14 @@ private boolean initialize_() { if (m_interval_tree_red == null) { m_interval_tree_red = new IntervalTreeImpl(true); - m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); } - m_interval_tree_red.startConstruction(); - for (int i = 0; i < m_envelopes_red.size(); i++) { - Envelope2D env = m_envelopes_red.get(i); - m_interval_tree_red.addInterval(env.xmin, env.xmax); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); } - m_interval_tree_red.endConstruction(); m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_red.resize(0); @@ -362,8 +369,7 @@ private boolean initialize_() { for (int i = 0; i < 2 * m_envelopes_red.size(); i++) m_sorted_end_indices_red.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, - 2 * m_envelopes_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_red, 0, 2 * m_envelopes_red.size(), true); m_sweep_index_red = 2 * m_envelopes_red.size(); @@ -384,16 +390,14 @@ private boolean initializeRed_() { if (m_interval_tree_red == null) { m_interval_tree_red = new IntervalTreeImpl(true); - m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); } - m_interval_tree_red.startConstruction(); - for (int i = 0; i < m_envelopes_red.size(); i++) { - Envelope2D env = m_envelopes_red.get(i); - m_interval_tree_red.addInterval(env.xmin, env.xmax); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); } - m_interval_tree_red.endConstruction(); m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_red.resize(0); @@ -401,8 +405,7 @@ private boolean initializeRed_() { for (int i = 0; i < 2 * m_envelopes_red.size(); i++) m_sorted_end_indices_red.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, - m_sorted_end_indices_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); m_sweep_index_red = m_sorted_end_indices_red.size(); if (m_queued_list_red != -1) { @@ -428,16 +431,14 @@ private boolean initializeBlue_() { if (m_interval_tree_blue == null) { m_interval_tree_blue = new IntervalTreeImpl(true); - m_iterator_blue = m_interval_tree_blue.getIterator(); m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); } - m_interval_tree_blue.startConstruction(); - for (int i = 0; i < m_envelopes_blue.size(); i++) { - Envelope2D env = m_envelopes_blue.get(i); - m_interval_tree_blue.addInterval(env.xmin, env.xmax); + m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); + + if (m_iterator_blue == null) { + m_iterator_blue = m_interval_tree_blue.getIterator(); } - m_interval_tree_blue.endConstruction(); m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); m_sorted_end_indices_blue.resize(0); @@ -445,8 +446,7 @@ private boolean initializeBlue_() { for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) m_sorted_end_indices_blue.add(i); - sortYEndIndices_(m_sorted_end_indices_blue, 0, - m_sorted_end_indices_blue.size(), false); + sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); m_sweep_index_blue = m_sorted_end_indices_blue.size(); if (m_queued_list_blue != -1) { @@ -472,29 +472,24 @@ private boolean initializeRedBlue_() { if (m_interval_tree_red == null) { m_interval_tree_red = new IntervalTreeImpl(true); - m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); } if (m_interval_tree_blue == null) { m_interval_tree_blue = new IntervalTreeImpl(true); - m_iterator_blue = m_interval_tree_blue.getIterator(); m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); } - m_interval_tree_red.startConstruction(); - for (int i = 0; i < m_envelopes_red.size(); i++) { - Envelope2D env = m_envelopes_red.get(i); - m_interval_tree_red.addInterval(env.xmin, env.xmax); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); } - m_interval_tree_red.endConstruction(); - m_interval_tree_blue.startConstruction(); - for (int i = 0; i < m_envelopes_blue.size(); i++) { - Envelope2D env = m_envelopes_blue.get(i); - m_interval_tree_blue.addInterval(env.xmin, env.xmax); + if (m_iterator_blue == null) { + m_iterator_blue = m_interval_tree_blue.getIterator(); } - m_interval_tree_blue.endConstruction(); m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); @@ -507,10 +502,8 @@ private boolean initializeRedBlue_() { for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) m_sorted_end_indices_blue.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, - m_sorted_end_indices_red.size(), true); - sortYEndIndices_(m_sorted_end_indices_blue, 0, - m_sorted_end_indices_blue.size(), false); + sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); m_sweep_index_red = m_sorted_end_indices_red.size(); m_sweep_index_blue = m_sorted_end_indices_blue.size(); @@ -533,8 +526,7 @@ private boolean initializeRedBlue_() { } private boolean sweep_() { - int y_end_point_handle = m_sorted_end_indices_red - .get(--m_sweep_index_red); + int y_end_point_handle = m_sorted_end_indices_red.get(--m_sweep_index_red); int envelope_handle = y_end_point_handle >> 1; if (isBottom_(y_end_point_handle)) { @@ -550,17 +542,14 @@ private boolean sweep_() { return true; } - m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, - m_envelopes_red.get(envelope_handle).xmax, m_tolerance); + m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, m_envelopes_red.get(envelope_handle).xmax, m_tolerance); m_envelope_handle_a = envelope_handle; m_function = State.iterate; return true; } - private boolean sweepBruteForce_() {// this isn't really a sweep, it just - // walks along the array of red - // envelopes backward. + private boolean sweepBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. if (--m_sweep_index_red == -1) { m_envelope_handle_a = -1; m_envelope_handle_b = -1; @@ -575,9 +564,7 @@ private boolean sweepBruteForce_() {// this isn't really a sweep, it just return true; } - private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it - // just walks along the array of - // red envelopes backward. + private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. if (--m_sweep_index_red == -1) { m_envelope_handle_a = -1; m_envelope_handle_b = -1; @@ -592,13 +579,9 @@ private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it return true; } - private boolean sweepRedBlue_() {// controls whether we want to sweep the - // red envelopes or sweep the blue - // envelopes - int y_end_point_handle_red = m_sorted_end_indices_red - .get(m_sweep_index_red - 1); - int y_end_point_handle_blue = m_sorted_end_indices_blue - .get(m_sweep_index_blue - 1); + private boolean sweepRedBlue_() {// controls whether we want to sweep the red envelopes or sweep the blue envelopes + int y_end_point_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red - 1); + int y_end_point_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue - 1); double y_red = getAdjustedValue_(y_end_point_handle_red, true); double y_blue = getAdjustedValue_(y_end_point_handle_blue, false); @@ -613,20 +596,16 @@ private boolean sweepRedBlue_() {// controls whether we want to sweep the if (isTop_(y_end_point_handle_blue)) return sweepBlue_(); - return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would - // also work correctly + return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would also work correctly } private boolean sweepRed_() { - int y_end_point_handle_red = m_sorted_end_indices_red - .get(--m_sweep_index_red); + int y_end_point_handle_red = m_sorted_end_indices_red.get(--m_sweep_index_red); int envelope_handle_red = y_end_point_handle_red >> 1; if (isBottom_(y_end_point_handle_red)) { - if (m_queued_list_red != -1 - && m_queued_indices_red.get(envelope_handle_red) != -1) { - m_queued_envelopes.deleteElement(m_queued_list_red, - m_queued_indices_red.get(envelope_handle_red)); + if (m_queued_list_red != -1 && m_queued_indices_red.get(envelope_handle_red) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_red, m_queued_indices_red.get(envelope_handle_red)); m_queued_indices_red.set(envelope_handle_red, -1); } else m_interval_tree_red.remove(envelope_handle_red); @@ -641,8 +620,7 @@ private boolean sweepRed_() { return true; } - if (m_queued_list_blue != -1 - && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { + if (m_queued_list_blue != -1 && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { int node = m_queued_envelopes.getFirst(m_queued_list_blue); while (node != -1) { int e = m_queued_envelopes.getData(node); @@ -655,9 +633,7 @@ private boolean sweepRed_() { } if (m_interval_tree_blue.size() > 0) { - m_iterator_blue.resetIterator( - m_envelopes_red.get(envelope_handle_red).xmin, - m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); + m_iterator_blue.resetIterator(m_envelopes_red.get(envelope_handle_red).xmin, m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); m_envelope_handle_a = envelope_handle_red; m_function = State.iterateBlue; } else { @@ -671,8 +647,7 @@ private boolean sweepRed_() { m_queued_list_red = m_queued_envelopes.createList(1); } - m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes - .addElement(m_queued_list_red, envelope_handle_red)); + m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes.addElement(m_queued_list_red, envelope_handle_red)); m_function = State.sweepRedBlue; } @@ -680,15 +655,12 @@ private boolean sweepRed_() { } private boolean sweepBlue_() { - int y_end_point_handle_blue = m_sorted_end_indices_blue - .get(--m_sweep_index_blue); + int y_end_point_handle_blue = m_sorted_end_indices_blue.get(--m_sweep_index_blue); int envelope_handle_blue = y_end_point_handle_blue >> 1; if (isBottom_(y_end_point_handle_blue)) { - if (m_queued_list_blue != -1 - && m_queued_indices_blue.get(envelope_handle_blue) != -1) { - m_queued_envelopes.deleteElement(m_queued_list_blue, - m_queued_indices_blue.get(envelope_handle_blue)); + if (m_queued_list_blue != -1 && m_queued_indices_blue.get(envelope_handle_blue) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_blue, m_queued_indices_blue.get(envelope_handle_blue)); m_queued_indices_blue.set(envelope_handle_blue, -1); } else m_interval_tree_blue.remove(envelope_handle_blue); @@ -703,8 +675,7 @@ private boolean sweepBlue_() { return true; } - if (m_queued_list_red != -1 - && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { + if (m_queued_list_red != -1 && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { int node = m_queued_envelopes.getFirst(m_queued_list_red); while (node != -1) { int e = m_queued_envelopes.getData(node); @@ -717,10 +688,7 @@ private boolean sweepBlue_() { } if (m_interval_tree_red.size() > 0) { - m_iterator_red.resetIterator( - m_envelopes_blue.get(envelope_handle_blue).xmin, - m_envelopes_blue.get(envelope_handle_blue).xmax, - m_tolerance); + m_iterator_red.resetIterator(m_envelopes_blue.get(envelope_handle_blue).xmin, m_envelopes_blue.get(envelope_handle_blue).xmax, m_tolerance); m_envelope_handle_b = envelope_handle_blue; m_function = State.iterateRed; } else { @@ -734,8 +702,7 @@ private boolean sweepBlue_() { m_queued_list_blue = m_queued_envelopes.createList(0); } - m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes - .addElement(m_queued_list_blue, envelope_handle_blue)); + m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes.addElement(m_queued_list_blue, envelope_handle_blue)); m_function = State.sweepRedBlue; } @@ -762,8 +729,7 @@ private boolean iterateRed_() { m_envelope_handle_a = -1; m_envelope_handle_b = -1; - int envelope_handle_blue = m_sorted_end_indices_blue - .get(m_sweep_index_blue) >> 1; + int envelope_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue) >> 1; m_interval_tree_blue.insert(envelope_handle_blue); m_function = State.sweepRedBlue; @@ -775,8 +741,7 @@ private boolean iterateBlue_() { if (m_envelope_handle_b != -1) return false; - int envelope_handle_red = m_sorted_end_indices_red - .get(m_sweep_index_red) >> 1; + int envelope_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; m_interval_tree_red.insert(envelope_handle_red); m_function = State.sweepRedBlue; @@ -886,18 +851,15 @@ private interface State { // *********** Helpers for Bucket sort************** private BucketSort m_bucket_sort; - private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, - int begin_, int end_, boolean b_red) { + private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { if (m_bucket_sort == null) m_bucket_sort = new BucketSort(); - Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper( - this, b_red); + Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper(this, b_red); m_bucket_sort.sort(end_indices, begin_, end_, sorter); } - private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, - int begin_, int end_, boolean b_red) { + private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { end_indices.Sort(begin_, end_, new EndPointsComparer(this, b_red)); } @@ -905,19 +867,17 @@ private double getAdjustedValue_(int e, boolean b_red) { double dy = 0.5 * m_tolerance; if (b_red) { Envelope2D envelope_red = m_envelopes_red.get(e >> 1); - double y = (isBottom_(e) ? envelope_red.ymin - dy - : envelope_red.ymax + dy); + double y = (isBottom_(e) ? envelope_red.ymin - dy : envelope_red.ymax + dy); return y; } Envelope2D envelope_blue = m_envelopes_blue.get(e >> 1); - double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax - + dy); + double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax + dy); return y; } - private static final class EndPointsComparer extends - AttributeStreamOfInt32.IntComparator {// For user sort + private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator {// For user sort + EndPointsComparer(Envelope2DIntersectorImpl intersector, boolean b_red) { m_intersector = intersector; m_b_red = b_red; @@ -939,10 +899,10 @@ public int compare(int e_1, int e_2) { } private static final class Envelope2DBucketSortHelper extends ClassicSort {// For - // bucket - // sort - Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, - boolean b_red) { + + // bucket + // sort + Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, boolean b_red) { m_intersector = intersector; m_b_red = b_red; } diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index c1960fd6..3f64b053 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -47,20 +47,23 @@ public final class Envelope3D implements Serializable{ public static Envelope3D construct(double _xmin, double _ymin, double _zmin, double _xmax, double _ymax, double _zmax) { - Envelope3D env = new Envelope3D(); - env.xmin = _xmin; - env.ymin = _ymin; - env.zmin = _zmin; - env.xmax = _xmax; - env.ymax = _ymax; - env.zmax = _zmax; + Envelope3D env = new Envelope3D(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); return env; } + public Envelope3D(double _xmin, double _ymin, double _zmin, double _xmax, double _ymax, double _zmax) { + setCoords(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); + } + public Envelope3D() { } + public Envelope3D(Envelope3D other) { + setCoords(other); + } + + public void setInfinite() { xmin = NumberUtils.negativeInf(); xmax = NumberUtils.positiveInf(); @@ -72,7 +75,11 @@ public void setInfinite() { public void setEmpty() { xmin = NumberUtils.NaN(); + ymin = NumberUtils.NaN(); zmin = NumberUtils.NaN(); + xmax = 0; + ymax = 0; + zmax = 0; } public boolean isEmpty() { @@ -99,6 +106,7 @@ public void setCoords(double _xmin, double _ymin, double _zmin, xmax = _xmax; ymax = _ymax; zmax = _zmax; + normalize(); } public void setCoords(double _x, double _y, double _z) { @@ -118,6 +126,24 @@ public void setCoords(Point3D center, double width, double height, ymax = ymin + height; zmin = center.z - depth * 0.5; zmax = zmin + depth; + normalize(); + } + + public void setCoords(Envelope3D envSrc) { + + setCoords(envSrc.xmin, envSrc.ymin, envSrc.zmin, envSrc.xmax, envSrc.ymax, envSrc.zmax); + } + + public double getWidth() { + return xmax - xmin; + } + + public double getHeight() { + return ymax - ymin; + } + + public double getDepth() { + return zmax - zmin; } public void move(Point3D vector) { @@ -129,6 +155,24 @@ public void move(Point3D vector) { zmax += vector.z; } + public void normalize() { + if (isEmpty()) + return; + + double min = Math.min(xmin, xmax); + double max = Math.max(xmin, xmax); + xmin = min; + xmax = max; + min = Math.min(ymin, ymax); + max = Math.max(ymin, ymax); + ymin = min; + ymax = max; + min = Math.min(zmin, zmax); + max = Math.max(zmin, zmax); + zmin = min; + zmax = max; + } + public void copyTo(Envelope2D env) { env.xmin = xmin; env.ymin = ymin; @@ -189,6 +233,93 @@ public void merge(double x1, double y1, double z1, double x2, double y2, merge(x2, y2, z2); } + public void inflate(double dx, double dy, double dz) { + if (isEmpty()) + return; + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + zmin -= dz; + zmax += dz; + if (xmin > xmax || ymin > ymax || zmin > zmax) + setEmpty(); + } + + /** + * Checks if this envelope intersects the other. + * + * @return True if this envelope intersects the other. + */ + public boolean isIntersecting(Envelope3D other) { + return !isEmpty() && !other.isEmpty() && ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap + ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin) && // check that y projections overlap + ((zmin <= other.zmin) ? zmax >= other.zmin : other.zmax >= zmin); // check that z projections overlap + } + + /** + * Intersects this envelope with the other and stores result in this + * envelope. + * + * @return True if this envelope intersects the other, otherwise sets this + * envelope to empty state and returns False. + */ + public boolean intersect(Envelope3D other) { + if (isEmpty() || other.isEmpty()) + return false; + + if (other.xmin > xmin) + xmin = other.xmin; + + if (other.xmax < xmax) + xmax = other.xmax; + + if (other.ymin > ymin) + ymin = other.ymin; + + if (other.ymax < ymax) + ymax = other.ymax; + + if (other.zmin > zmin) + zmin = other.zmin; + + if (other.zmax < zmax) + zmax = other.zmax; + + boolean bIntersecting = xmin <= xmax && ymin <= ymax && zmin <= zmax; + + if (!bIntersecting) + setEmpty(); + + return bIntersecting; + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * inclusive). + */ + public boolean contains(Envelope3D other) {// Note: Will return False, if either envelope is empty. + return other.xmin >= xmin && other.xmax <= xmax && other.ymin >= ymin && other.ymax <= ymax && other.zmin >= zmin && other.zmax <= zmax; + } + + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope3D)) + return false; + + Envelope3D other = (Envelope3D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (xmin != other.xmin || ymin != other.ymin || zmin != other.zmin || xmax != other.xmax || ymax != other.ymax || zmax != other.zmax) + return false; + + return true; + } + public void construct(Envelope1D xinterval, Envelope1D yinterval, Envelope1D zinterval) { if (xinterval.isEmpty() || yinterval.isEmpty()) { diff --git a/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java b/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java new file mode 100644 index 00000000..c6bd2d09 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java @@ -0,0 +1,94 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for MultiPoint, Polyline, and Polygon +public class GenericGeometrySerializer implements Serializable { + private static final long serialVersionUID = 1L; + int geometryType; + byte[] esriShape = null; + int simpleFlag = 0; + double tolerance = 0; + boolean[] ogcFlags = null; + + public Object readResolve() throws ObjectStreamException { + Geometry geometry = null; + try { + geometry = GeometryEngine.geometryFromEsriShape( + esriShape, Geometry.Type.intToType(geometryType)); + + if (Geometry.isMultiVertex(geometryType)) { + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryType)) { + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + if (ogcFlags[i]) + pathFlags.setBits(i, + (byte) PathFlags.enumOGCStartPolygon); + } + } + mvImpl.setIsSimple(simpleFlag, tolerance, false); + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + return geometry; + } + + public void setGeometryByValue(Geometry geometry) + throws ObjectStreamException { + try { + esriShape = GeometryEngine + .geometryToEsriShape(geometry); + geometryType = geometry.getType().value(); + if (Geometry.isMultiVertex(geometryType)) { + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + tolerance = mvImpl.m_simpleTolerance; + simpleFlag = mvImpl.getIsSimple(0); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryType)) { + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + ogcFlags = new boolean[mpImpl.getPathCount()]; + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; + } + } + + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 108e04e0..b7845151 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -257,7 +257,7 @@ static public void geodesic_distance_ngs(double a, double e2, double lam1, /* top of the long-line loop (kind = 1) */ q_continue_looping = true; - while (q_continue_looping == true) { + while (q_continue_looping && it < 100) { it = it + 1; if (kind == 1) { diff --git a/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java b/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java new file mode 100644 index 00000000..aa35f20d --- /dev/null +++ b/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java @@ -0,0 +1,183 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.Arrays; + +class GeoJsonCrsTables { + static int getWkidFromCrsShortForm(String crs_identifier) { + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version + + if (last_colon == -1) + return -1; + + int code_start = last_colon + 1; + int wkid = getWkidFromCrsCode_(crs_identifier, code_start); + return wkid; + } + + static int getWkidFromCrsName(String crs_identifier) { + int wkid = -1; + + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip + // authority, + // version, and + // other things. + // Just try to + // get a wkid. + // This works + // for + // short/long + // form. + + if (last_colon == -1) + return -1; + + int code_start = last_colon + 1; + wkid = getWkidFromCrsCode_(crs_identifier, code_start); + + if (wkid != -1) + return wkid; + + wkid = getWkidFromCrsOgcUrn(crs_identifier); // could be an OGC + // "preferred" urn + return wkid; + } + + static int getWkidFromCrsOgcUrn(String crs_identifier) { + int wkid = -1; + if (crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)) + wkid = getWkidFromCrsOgcUrn_(crs_identifier); + + return wkid; + } + + private static int getWkidFromCrsCode_(String crs_identifier, int code_start) { + assert(code_start > 0); + + int wkid = -1; + int code_count = crs_identifier.length() - code_start; + + try { + wkid = Integer.parseInt(crs_identifier.substring(code_start, code_start + code_count)); + } catch (Exception e) { + } + + return (int) wkid; + } + + private static int getWkidFromCrsOgcUrn_(String crs_identifier) { + assert(crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)); + + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version + + if (last_colon == -1) + return -1; + + int ogc_code_start = last_colon + 1; + int ogc_code_count = crs_identifier.length() - ogc_code_start; + + if (crs_identifier.regionMatches(ogc_code_start, "CRS84", 0, ogc_code_count)) + return 4326; + + if (crs_identifier.regionMatches(ogc_code_start, "CRS83", 0, ogc_code_count)) + return 4269; + + if (crs_identifier.regionMatches(ogc_code_start, "CRS27", 0, ogc_code_count)) + return 4267; + + return -1; + } + + static int getWkidFromCrsHref(String crs_identifier) { + int sr_org_code_start = -1; + + if (crs_identifier.regionMatches(0, "http://spatialreference.org/ref/epsg/", 0, 37)) + sr_org_code_start = 37; + else if (crs_identifier.regionMatches(0, "www.spatialreference.org/ref/epsg/", 0, 34)) + sr_org_code_start = 34; + else if (crs_identifier.regionMatches(0, "http://www.spatialreference.org/ref/epsg/", 0, 41)) + sr_org_code_start = 41; + + if (sr_org_code_start != -1) { + int sr_org_code_end = crs_identifier.indexOf('/', sr_org_code_start); + + if (sr_org_code_end == -1) + return -1; + + int count = sr_org_code_end - sr_org_code_start; + int wkid = -1; + + try { + wkid = Integer.parseInt(crs_identifier.substring(sr_org_code_start, sr_org_code_start + count)); + } catch (Exception e) { + } + + return wkid; + } + + int open_gis_epsg_slash_end = -1; + + if (crs_identifier.regionMatches(0, "http://opengis.net/def/crs/EPSG/", 0, 32)) + open_gis_epsg_slash_end = 32; + else if (crs_identifier.regionMatches(0, "www.opengis.net/def/crs/EPSG/", 0, 29)) + open_gis_epsg_slash_end = 29; + else if (crs_identifier.regionMatches(0, "http://www.opengis.net/def/crs/EPSG/", 0, 36)) + open_gis_epsg_slash_end = 36; + + if (open_gis_epsg_slash_end != -1) { + int last_slash = crs_identifier.lastIndexOf('/'); // skip over the + // "0/" + + if (last_slash == -1) + return -1; + + int open_gis_code_start = last_slash + 1; + + int count = crs_identifier.length() - open_gis_code_start; + int wkid = -1; + + try { + wkid = Integer.parseInt(crs_identifier.substring(open_gis_code_start, open_gis_code_start + count)); + } catch (Exception e) { + } + + return wkid; + } + + if (crs_identifier.compareToIgnoreCase("http://spatialreference.org/ref/sr-org/6928/ogcwkt/") == 0) + return 3857; + + return -1; + } + + static String getWktFromCrsName(String crs_identifier) { + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip + // authority + int wkt_start = last_colon + 1; + int wkt_count = crs_identifier.length() - wkt_start; + String wkt = crs_identifier.substring(wkt_start, wkt_start + wkt_count); + return wkt; + } +} diff --git a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java index 027bf2ac..6290a0f6 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java @@ -24,6 +24,31 @@ package com.esri.core.geometry; public interface GeoJsonExportFlags { - static final int geoJsonExportDefaults = 0; - static final int geoJsonExportPreferMultiGeometry = 1; + public static final int geoJsonExportDefaults = 0; + /** + * Export MultiXXX geometries every time, by default it will export the minimum required type. + */ + public static final int geoJsonExportPreferMultiGeometry = 1; + public static final int geoJsonExportStripZs = 2; + public static final int geoJsonExportStripMs = 4; + public static final int geoJsonExportSkipCRS = 8; + public static final int geoJsonExportFailIfNotSimple = 16; + public static final int geoJsonExportPrecision16 = 0x02000; + public static final int geoJsonExportPrecision15 = 0x04000; + public static final int geoJsonExportPrecision14 = 0x06000; + public static final int geoJsonExportPrecision13 = 0x08000; + public static final int geoJsonExportPrecision12 = 0x0a000; + public static final int geoJsonExportPrecision11 = 0x0c000; + public static final int geoJsonExportPrecision10 = 0x0e000; + public static final int geoJsonExportPrecision9 = 0x10000; + public static final int geoJsonExportPrecision8 = 0x12000; + public static final int geoJsonExportPrecision7 = 0x14000; + public static final int geoJsonExportPrecision6 = 0x16000; + public static final int geoJsonExportPrecision5 = 0x18000; + public static final int geoJsonExportPrecision4 = 0x1a000; + public static final int geoJsonExportPrecision3 = 0x1c000; + public static final int geoJsonExportPrecision2 = 0x1e000; + public static final int geoJsonExportPrecision1 = 0x20000; + public static final int geoJsonExportPrecision0 = 0x22000; + public static final int geoJsonExportPrecisionFixedPoint = 0x40000; } diff --git a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java index a464d20b..5245bd96 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java @@ -24,6 +24,15 @@ package com.esri.core.geometry; public interface GeoJsonImportFlags { - static final int geoJsonImportDefaults = 0; - static final int geoJsonImportNonTrusted = 2; + public static final int geoJsonImportDefaults = 0; + @Deprecated static final int geoJsonImportNonTrusted = 2; + /** + * If set, the import will skip CRS. + */ + public static final int geoJsonImportSkipCRS = 8; + /** + * If set, and the geojson does not have a spatial reference, the result geometry will not have one too, otherwise + * it'll assume WGS84. + */ + public static final int geoJsonImportNoWGS84Default = 16; } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index e5d08afa..68e93671 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -35,10 +35,6 @@ * objects that define a spatial location and and associated geometric shape. */ public abstract class Geometry implements Serializable { - // Note: We use writeReplace with GeometrySerializer. This field is - // irrelevant. Need to be removed after final. - private static final long serialVersionUID = 2L; - VertexDescription m_description; volatile int m_touchFlag; @@ -117,6 +113,18 @@ public int value() { Type(int val) { enumValue = val; } + + static public Geometry.Type intToType(int geometryType) + { + Geometry.Type[] v = Geometry.Type.values(); + for(int i = 0; i < v.length; i++) + { + if(v[i].value() == geometryType) + return v[i]; + } + + throw new IllegalArgumentException(); + } } /** @@ -153,7 +161,7 @@ public VertexDescription getDescription() { * Assigns the new VertexDescription by adding or dropping attributes. The * Geometry will have the src description as a result. */ - void assignVertexDescription(VertexDescription src) { + public void assignVertexDescription(VertexDescription src) { _touch(); if (src == m_description) return; @@ -161,14 +169,14 @@ void assignVertexDescription(VertexDescription src) { _assignVertexDescriptionImpl(src); } - protected abstract void _assignVertexDescriptionImpl(VertexDescription src); + protected abstract void _assignVertexDescriptionImpl(VertexDescription src); /** * Merges the new VertexDescription by adding missing attributes from the * src. The Geometry will have a union of the current and the src * descriptions. */ - void mergeVertexDescription(VertexDescription src) { + public void mergeVertexDescription(VertexDescription src) { _touch(); if (src == m_description) return; @@ -566,7 +574,27 @@ static public enum GeometryAccelerationDegree { } Object writeReplace() throws ObjectStreamException { - GeometrySerializer geomSerializer = new GeometrySerializer(); + Type gt = getType(); + if (gt == Geometry.Type.Point) + { + PtSrlzr pt = new PtSrlzr(); + pt.setGeometryByValue((Point)this); + return pt; + } + else if (gt == Geometry.Type.Envelope) + { + EnvSrlzr e = new EnvSrlzr(); + e.setGeometryByValue((Envelope)this); + return e; + } + else if (gt == Geometry.Type.Line) + { + LnSrlzr ln = new LnSrlzr(); + ln.setGeometryByValue((Line)this); + return ln; + } + + GenericGeometrySerializer geomSerializer = new GenericGeometrySerializer(); geomSerializer.setGeometryByValue(this); return geomSerializer; } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index e027dd6c..7bed73ea 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -242,6 +242,7 @@ public static Geometry geometryFromWkt(String wkt, int importFlags, * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. * @throws IllegalArgument exception if an error is found while parsing the geoJson string. */ + @Deprecated public static MapGeometry geometryFromGeoJson(String geoJson, int importFlags, Geometry.Type geometryType) throws JSONException { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory diff --git a/src/main/java/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java index b91c16f7..a2fefa1e 100644 --- a/src/main/java/com/esri/core/geometry/GeometrySerializer.java +++ b/src/main/java/com/esri/core/geometry/GeometrySerializer.java @@ -27,6 +27,8 @@ import java.io.ObjectStreamException; import java.io.Serializable; +//Left here for backward compatibility. Use GenericGeometrySerializer instead +@Deprecated final class GeometrySerializer implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index acdc10d8..f7248c47 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -26,504 +26,452 @@ import java.util.ArrayList; final class IntervalTreeImpl { - static final class IntervalTreeIteratorImpl { - /** - * Resets the iterator to a starting state on the Interval_tree_impl - * using the input Envelope_1D interval as the query \param query The - * Envelope_1D interval used for the query. \param tolerance The - * tolerance used for the intersection tests. - */ - void resetIterator(Envelope1D query, double tolerance) { - m_query.vmin = query.vmin - tolerance; - m_query.vmax = query.vmax + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; - } + private void sortEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { + IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper(this); + BucketSort bucket_sort = new BucketSort(); + bucket_sort.sort(end_indices, begin_, end_, sorter); + } - /** - * Resets the iterator to a starting state on the Interval_tree_impl - * using the input Envelope_1D interval as the query \param query The - * Envelope_1D interval used for the query. \param tolerance The - * tolerance used for the intersection tests. - */ - void resetIterator(double query_min, double query_max, double tolerance) { - if (query_min > query_max) - throw new IllegalArgumentException(); + private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { + end_indices.Sort(begin_, end_, new EndPointsComparer(this)); + } - m_query.vmin = query_min - tolerance; - m_query.vmax = query_max + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; + private double getValue_(int e) { + if (!m_b_envelopes_ref) { + Envelope1D interval = m_intervals.get(e >> 1); + double v = (isLeft_(e) ? interval.vmin : interval.vmax); + return v; } - /** - * Resets the iterator to a starting state on the Interval_tree_impl - * using the input double as the stabbing query \param query The double - * used for the query. \param tolerance The tolerance used for the - * intersection tests. - */ - void resetIterator(double query, double tolerance) { - m_query.vmin = query - tolerance; - m_query.vmax = query + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; + Envelope2D interval = m_envelopes_ref.get(e >> 1); + double v = (isLeft_(e) ? interval.xmin : interval.xmax); + return v; + } + + private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator { // For user sort + + EndPointsComparer(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; } - /** - * Iterates over all intervals which interset the query interval. - * Returns an index to an interval that intersects the query. - */ - int next() { - if (!m_interval_tree.m_b_construction_ended) - throw new GeometryException("invalid call"); + @Override + public int compare(int e_1, int e_2) { + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); - if (m_function_index < 0) + if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) return -1; - boolean b_searching = true; - - while (b_searching) { - switch (m_function_stack[m_function_index]) { - case State.pIn: - b_searching = pIn_(); - break; - case State.pL: - b_searching = pL_(); - break; - case State.pR: - b_searching = pR_(); - break; - case State.pT: - b_searching = pT_(); - break; - case State.right: - b_searching = right_(); - break; - case State.left: - b_searching = left_(); - break; - case State.all: - b_searching = all_(); - break; - case State.initialize: - b_searching = initialize_(); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } + return 1; + } - if (m_current_end_handle != -1) - return getCurrentEndIndex_() >> 1; + private IntervalTreeImpl m_interval_tree; + } - return -1; - } + private class IntervalTreeBucketSortHelper extends ClassicSort { // For bucket sort - // Creates an iterator on the input Interval_tree using the input - // Envelope_1D interval as the query. - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, - Envelope1D query, double tolerance) { + IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - resetIterator(query, tolerance); } - // Creates an iterator on the input Interval_tree using the input double - // as the stabbing query. - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, - double tolerance) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - resetIterator(query, tolerance); + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_interval_tree.sortEndIndicesHelper_(indices, begin, end); } - // Creates an iterator on the input Interval_tree. - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - m_function_index = -1; + @Override + public double getValue(int e) { + return m_interval_tree.getValue_(e); } private IntervalTreeImpl m_interval_tree; - private Envelope1D m_query = new Envelope1D(); - private int m_primary_handle; - private int m_next_primary_handle; - private int m_forked_handle; - private int m_current_end_handle; - private int m_next_end_handle; - private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32( - 0); - private int m_function_index; - private int[] m_function_stack = new int[2]; + } - private interface State { - static final int initialize = 0; - static final int pIn = 1; - static final int pL = 2; - static final int pR = 3; - static final int pT = 4; - static final int right = 5; - static final int left = 6; - static final int all = 7; - } + IntervalTreeImpl(boolean b_offline_dynamic) { + m_b_envelopes_ref = false; + m_b_offline_dynamic = b_offline_dynamic; + m_b_constructing = false; + m_b_construction_ended = false; + } - private boolean initialize_() { - m_primary_handle = -1; - m_next_primary_handle = -1; - m_forked_handle = -1; - m_current_end_handle = -1; + void addEnvelopesRef(ArrayList envelopes) { + reset_(true, true); + m_b_envelopes_ref = true; + m_envelopes_ref = envelopes; - if (m_interval_tree.m_primary_nodes != null - && m_interval_tree.m_primary_nodes.size() > 0) { - m_function_stack[0] = State.pIn; // overwrite initialize - m_next_primary_handle = m_interval_tree.m_root; - return true; - } + m_b_constructing = false; + m_b_construction_ended = true; - m_function_index = -1; - return false; + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_envelopes_ref.size(); } + } - private boolean pIn_() { - m_primary_handle = m_next_primary_handle; + void startConstruction() { + reset_(true, false); + } - if (m_primary_handle == -1) { - m_function_index = -1; - m_current_end_handle = -1; - return false; - } + void addInterval(Envelope1D interval) { + if (!m_b_constructing) + throw new GeometryException("invalid call"); - double discriminant = m_interval_tree - .getDiscriminant_(m_primary_handle); + m_intervals.add(interval); + } - if (m_query.vmax < discriminant) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getLPTR_(m_primary_handle); + void addInterval(double min, double max) { + if (!m_b_constructing) + throw new GeometryException("invald call"); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.left; - } + m_intervals.add(new Envelope1D(min, max)); + } - return true; - } + void endConstruction() { + if (!m_b_constructing) + throw new GeometryException("invalid call"); - if (discriminant < m_query.vmin) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getRPTR_(m_primary_handle); + m_b_constructing = false; + m_b_construction_ended = true; - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getLast_(secondary_handle); - m_function_stack[++m_function_index] = State.right; - } + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_intervals.size(); + } + } - return true; - } + /* + * Resets the Interval_tree_impl to an empty state, but maintains a handle + * on the current intervals. + */ + void reset() { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new IllegalArgumentException("invalid call"); - assert (m_query.contains(discriminant)); + reset_(false, m_b_envelopes_ref); + } - m_function_stack[m_function_index] = State.pL; // overwrite pIn - m_forked_handle = m_primary_handle; - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree.getLPTR_(m_primary_handle); + /** + * Returns the number of intervals stored in the Interval_tree_impl + */ + int size() { + return m_c_count; + } - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + /** + * Gets an iterator on the Interval_tree_impl using the input Envelope_1D + * interval as the query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval + * used for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); + } - return true; - } + /** + * Gets an iterator on the Interval_tree_impl using the input double as the + * stabbing query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The double used for the + * stabbing query. \param tolerance The tolerance used for the intersection + * tests. + */ + IntervalTreeIteratorImpl getIterator(double query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); + } - private boolean pL_() { - m_primary_handle = m_next_primary_handle; + /** + * Gets an iterator on the Interval_tree_impl. + */ + IntervalTreeIteratorImpl getIterator() { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); + } - if (m_primary_handle == -1) { - m_function_stack[m_function_index] = State.pR; // overwrite pL - m_next_primary_handle = m_interval_tree - .getRPTR_(m_forked_handle); - return true; - } + private boolean m_b_envelopes_ref; + private boolean m_b_offline_dynamic; + private ArrayList m_intervals; + private ArrayList m_envelopes_ref; + private StridedIndexTypeCollection m_tertiary_nodes; // 5 elements for offline dynamic case, 4 elements for static case + private StridedIndexTypeCollection m_interval_nodes; // 3 elements + private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic// case + private IndexMultiDCList m_secondary_lists; // for static case + private Treap m_secondary_treaps; // for off-line dynamic case + private AttributeStreamOfInt32 m_end_indices_unique; // for both offline dynamic and static cases + private int m_c_count; + private int m_root; + private boolean m_b_sort_intervals; + private boolean m_b_constructing; + private boolean m_b_construction_ended; - double discriminant = m_interval_tree - .getDiscriminant_(m_primary_handle); + /* m_tertiary_nodes + * 0: m_discriminant_index_1 + * 1: m_secondary + * 2: m_lptr + * 3: m_rptr + * 4: m_pptr + */ - if (discriminant < m_query.vmin) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getRPTR_(m_primary_handle); + private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getLast_(secondary_handle); - m_function_stack[++m_function_index] = State.right; - } + for (int i = 0; i < 2 * size; i++) + end_indices.add(i); - return true; - } + sortEndIndices_(end_indices, 0, 2 * size); + } - assert (m_query.contains(discriminant)); + private void querySortedDuplicatesRemoved_(AttributeStreamOfInt32 end_indices_sorted) { + // remove duplicates - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree.getLPTR_(m_primary_handle); + double prev = NumberUtils.TheNaN; + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + double v = getValue_(e); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; + if (v != prev) { + m_end_indices_unique.add(e); + prev = v; } + } + } - int rptr = m_interval_tree.getRPTR_(m_primary_handle); + void insert(int index) { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new IllegalArgumentException("invalid call"); - if (rptr != -1) { - m_tertiary_stack.add(rptr); // we'll search this in the pT state - } + if (m_root == -1) { - return true; - } + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - private boolean pR_() { - m_primary_handle = m_next_primary_handle; + if (m_b_sort_intervals) { + // sort + AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32(0); + end_point_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_point_indices_sorted); - if (m_primary_handle == -1) { - m_function_stack[m_function_index] = State.pT; // overwrite pR - return true; + // remove duplicates + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_point_indices_sorted); + m_interval_handles.resize(size, -1); + m_interval_handles.setRange(-1, 0, size); + m_b_sort_intervals = false; + } else { + m_interval_handles.setRange(-1, 0, size); } - double discriminant = m_interval_tree - .getDiscriminant_(m_primary_handle); + m_root = createRoot_(); + } - if (m_query.vmax < discriminant) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getLPTR_(m_primary_handle); + int interval_handle = insertIntervalEnd_(index << 1, m_root); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, secondary_handle); + setRightEnd_(interval_handle, right_end_handle); + m_interval_handles.set(index, interval_handle); + m_c_count++; + // assert(check_validation_()); + } - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.left; - } + private void insertIntervalsStatic_() { + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - return true; - } + assert (m_b_sort_intervals); - assert (m_query.contains(discriminant)); + // sort + AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32(0); + end_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_indices_sorted); - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); + // remove duplicates + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_indices_sorted); - m_next_primary_handle = m_interval_tree.getRPTR_(m_primary_handle); + assert (m_tertiary_nodes.size() == 0); + m_interval_nodes.setCapacity(size); // one for each interval being inserted. each element contains a tertiary node, a left secondary node, and a right secondary node. + m_secondary_lists.reserveNodes(2 * size); // one for each end point of the original interval set (not the unique set) - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(size); + interval_handles.setRange(-1, 0, size); - int lptr = m_interval_tree.getLPTR_(m_primary_handle); + m_root = createRoot_(); - if (lptr != -1) { - m_tertiary_stack.add(lptr); // we'll search this in the pT state - } + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + int interval_handle = interval_handles.get(e >> 1); - return true; + if (interval_handle != -1) {// insert the right end point + assert (isRight_(e)); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + setRightEnd_(interval_handle, m_secondary_lists.addElement(secondary_handle, e)); + } else {// insert the left end point + assert (isLeft_(e)); + interval_handle = insertIntervalEnd_(e, m_root); + interval_handles.set(e >> 1, interval_handle); + } } - private boolean pT_() { - if (m_tertiary_stack.size() == 0) { - m_function_index = -1; - m_current_end_handle = -1; - return false; - } + assert (m_secondary_lists.getNodeCount() == 2 * size); + } - m_primary_handle = m_tertiary_stack - .get(m_tertiary_stack.size() - 1); - m_tertiary_stack.resize(m_tertiary_stack.size() - 1); + private int createRoot_() { + int discriminant_index_1 = calculateDiscriminantIndex1_(0, m_end_indices_unique.size() - 1); + return createTertiaryNode_(discriminant_index_1); + } - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); + private int insertIntervalEnd_(int end_index, int root) { + assert (isLeft_(end_index)); + int pptr = -1; + int ptr = root; + int secondary_handle = -1; + int interval_handle = -1; + int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; + int index = end_index >> 1; + double discriminant_pptr = NumberUtils.NaN(); + double discriminant_ptr = NumberUtils.NaN(); + boolean bSearching = true; - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + double min = getMin_(index); + double max = getMax_(index); - if (m_interval_tree.getLPTR_(m_primary_handle) != -1) - m_tertiary_stack - .add(m_interval_tree.getLPTR_(m_primary_handle)); + int discriminant_index_1 = -1; - if (m_interval_tree.getRPTR_(m_primary_handle) != -1) - m_tertiary_stack - .add(m_interval_tree.getRPTR_(m_primary_handle)); + while (bSearching) { + im = il + (ir - il) / 2; + assert (il != ir || min == max); + discriminant_index_1 = calculateDiscriminantIndex1_(il, ir); + double discriminant = getDiscriminantFromIndex1_(discriminant_index_1); + assert (!NumberUtils.isNaN(discriminant)); - return true; - } + if (max < discriminant) { + if (ptr != -1) { + if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { + assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); - private boolean left_() { - m_current_end_handle = m_next_end_handle; + pptr = ptr; + discriminant_pptr = discriminant; + ptr = getLPTR_(ptr); - if (m_current_end_handle != -1 - && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) - && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { - m_next_end_handle = getNext_(); - return false; - } + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.NaN(); + } else if (discriminant_ptr > discriminant) { + int tertiary_handle = createTertiaryNode_(discriminant_index_1); - m_function_index--; - return true; - } + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); - private boolean right_() { - m_current_end_handle = m_next_end_handle; + setRPTR_(tertiary_handle, ptr); - if (m_current_end_handle != -1 - && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) - && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { - m_next_end_handle = getPrev_(); - return false; - } + if (m_b_offline_dynamic) { + setPPTR_(tertiary_handle, pptr); + setPPTR_(ptr, tertiary_handle); + } - m_function_index--; - return true; - } + pptr = tertiary_handle; + discriminant_pptr = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.NaN(); + } + } - private boolean all_() { - m_current_end_handle = m_next_end_handle; + ir = im; - if (m_current_end_handle != -1 - && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { - m_next_end_handle = getNext_(); - return false; + continue; } - m_function_index--; - return true; - } - - private int getNext_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists - .getNext(m_current_end_handle); - - return m_interval_tree.m_secondary_treaps - .getNext(m_current_end_handle); - } + if (min > discriminant) { + if (ptr != -1) { + if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { + assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); - private int getPrev_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists - .getPrev(m_current_end_handle); + pptr = ptr; + discriminant_pptr = discriminant; + ptr = getRPTR_(ptr); - return m_interval_tree.m_secondary_treaps - .getPrev(m_current_end_handle); - } + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.NaN(); + } else if (discriminant_ptr < discriminant) { + int tertiary_handle = createTertiaryNode_(discriminant_index_1); - private int getCurrentEndIndex_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists - .getData(m_current_end_handle); + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); - return m_interval_tree.m_secondary_treaps - .getElement(m_current_end_handle); - } - } + setLPTR_(tertiary_handle, ptr); - IntervalTreeImpl(boolean b_offline_dynamic) { - m_b_offline_dynamic = b_offline_dynamic; - m_b_constructing = false; - m_b_construction_ended = false; - } + if (m_b_offline_dynamic) { + setPPTR_(tertiary_handle, pptr); + setPPTR_(ptr, tertiary_handle); + } - void startConstruction() { - reset_(true); - } + pptr = tertiary_handle; + discriminant_pptr = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.NaN(); + } + } - void addInterval(Envelope1D interval) { - if (!m_b_constructing) - throw new GeometryException("invalid call"); + il = im + 1; - m_intervals.add(interval); - } + continue; + } - void addInterval(double min, double max) { - if (!m_b_constructing) - throw new GeometryException("invald call"); + int tertiary_handle = -1; - m_intervals.add(new Envelope1D(min, max)); - } + if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { + tertiary_handle = createTertiaryNode_(discriminant_index_1); + } else { + tertiary_handle = ptr; + } - void endConstruction() { - if (!m_b_constructing) - throw new GeometryException("invalid call"); + secondary_handle = getSecondaryFromTertiary_(tertiary_handle); - m_b_constructing = false; - m_b_construction_ended = true; + if (secondary_handle == -1) { + secondary_handle = createSecondary_(tertiary_handle); + setSecondaryToTertiary_(tertiary_handle, secondary_handle); + } - if (!m_b_offline_dynamic) { - insertIntervalsStatic_(); - m_c_count = m_intervals.size(); - } - } + int left_end_handle = addEndIndex_(secondary_handle, end_index); + interval_handle = createIntervalNode_(); + setSecondaryToInterval_(interval_handle, secondary_handle); + setLeftEnd_(interval_handle, left_end_handle); - /** - * Inserts the interval from the given index into the Interval_tree_impl. - * This operation can only be performed in the offline dynamic case. \param - * index The index containing the interval to be inserted. - */ - void insert(int index) { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new IllegalArgumentException("invalid call"); + if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { + assert (tertiary_handle != -1); + assert (getLPTR_(tertiary_handle) == -1 && getRPTR_(tertiary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(tertiary_handle) == -1)); - if (m_root == -1) { + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); - int size = m_intervals.size(); + if (m_b_offline_dynamic) + setPPTR_(tertiary_handle, pptr); - if (m_b_sort_intervals) { - // sort - AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32( - 0); - end_point_indices_sorted.reserve(2 * size); - querySortedEndPointIndices_(end_point_indices_sorted); + if (ptr != -1) { + if (discriminant_ptr < discriminant) + setLPTR_(tertiary_handle, ptr); + else + setRPTR_(tertiary_handle, ptr); - // remove duplicates - m_end_indices_unique.reserve(2 * size); - m_end_indices_unique.resize(0); - querySortedDuplicatesRemoved_(end_point_indices_sorted); - m_interval_handles.resize(size, -1); - m_interval_handles.setRange(-1, 0, size); - m_b_sort_intervals = false; - } else { - m_interval_handles.setRange(-1, 0, size); + if (m_b_offline_dynamic) + setPPTR_(ptr, tertiary_handle); + } } - m_root = createPrimaryNode_(); + bSearching = false; + break; } - int interval_handle = insertIntervalEnd_(index << 1, m_root); - int secondary_handle = getSecondaryFromInterval_(interval_handle); - int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, - secondary_handle); - setRightEnd_(interval_handle, right_end_handle); - m_interval_handles.set(index, interval_handle); - m_c_count++; - // assert(check_validation_()); + return interval_handle; } - /** - * Deletes the interval from the Interval_tree_impl. \param index The index - * containing the interval to be deleted from the Interval_tree_impl. - */ void remove(int index) { if (!m_b_offline_dynamic || !m_b_construction_ended) throw new GeometryException("invalid call"); @@ -531,8 +479,7 @@ void remove(int index) { int interval_handle = m_interval_handles.get(index); if (interval_handle == -1) - throw new IllegalArgumentException( - "the interval does not exist in the interval tree"); + throw new GeometryException("the interval does not exist in the interval tree"); m_interval_handles.set(index, -1); @@ -544,574 +491,595 @@ void remove(int index) { int size; int secondary_handle = getSecondaryFromInterval_(interval_handle); - int primary_handle; + int tertiary_handle = -1; - primary_handle = m_secondary_treaps.getTreapData(secondary_handle); - m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), - secondary_handle); - m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), - secondary_handle); + tertiary_handle = m_secondary_treaps.getTreapData(secondary_handle); + m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), secondary_handle); + m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), secondary_handle); size = m_secondary_treaps.size(secondary_handle); if (size == 0) { m_secondary_treaps.deleteTreap(secondary_handle); - setSecondaryToPrimary_(primary_handle, -1); + setSecondaryToTertiary_(tertiary_handle, -1); } m_interval_nodes.deleteElement(interval_handle); - int tertiary_handle = getPPTR_(primary_handle); - int lptr = getLPTR_(primary_handle); - int rptr = getRPTR_(primary_handle); + int pptr = getPPTR_(tertiary_handle); + int lptr = getLPTR_(tertiary_handle); + int rptr = getRPTR_(tertiary_handle); int iterations = 0; - while (!(size > 0 || primary_handle == m_root || (lptr != -1 && rptr != -1))) { + while (!(size > 0 || tertiary_handle == m_root || (lptr != -1 && rptr != -1))) { assert (size == 0); assert (lptr == -1 || rptr == -1); - assert (primary_handle != 0); + assert (tertiary_handle != 0); - if (primary_handle == getLPTR_(tertiary_handle)) { + if (tertiary_handle == getLPTR_(pptr)) { if (lptr != -1) { - setLPTR_(tertiary_handle, lptr); - setPPTR_(lptr, tertiary_handle); - setLPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); + setLPTR_(pptr, lptr); + setPPTR_(lptr, pptr); + setLPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); } else if (rptr != -1) { - setLPTR_(tertiary_handle, rptr); - setPPTR_(rptr, tertiary_handle); - setRPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); + setLPTR_(pptr, rptr); + setPPTR_(rptr, pptr); + setRPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); } else { - setLPTR_(tertiary_handle, -1); - setPPTR_(primary_handle, -1); + setLPTR_(pptr, -1); + setPPTR_(tertiary_handle, -1); } } else { if (lptr != -1) { - setRPTR_(tertiary_handle, lptr); - setPPTR_(lptr, tertiary_handle); - setLPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); + setRPTR_(pptr, lptr); + setPPTR_(lptr, pptr); + setLPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); } else if (rptr != -1) { - setRPTR_(tertiary_handle, rptr); - setPPTR_(rptr, tertiary_handle); - setRPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); - } else { + setRPTR_(pptr, rptr); + setPPTR_(rptr, pptr); setRPTR_(tertiary_handle, -1); - setPPTR_(primary_handle, -1); + setPPTR_(tertiary_handle, -1); + } else { + setRPTR_(pptr, -1); + setPPTR_(tertiary_handle, -1); } } + m_tertiary_nodes.deleteElement(tertiary_handle); + iterations++; - primary_handle = tertiary_handle; - secondary_handle = getSecondaryFromPrimary(primary_handle); - size = (secondary_handle != -1 ? m_secondary_treaps - .size(secondary_handle) : 0); - lptr = getLPTR_(primary_handle); - rptr = getRPTR_(primary_handle); - tertiary_handle = getPPTR_(primary_handle); + tertiary_handle = pptr; + secondary_handle = getSecondaryFromTertiary_(tertiary_handle); + size = (secondary_handle != -1 ? m_secondary_treaps.size(secondary_handle) : 0); + lptr = getLPTR_(tertiary_handle); + rptr = getRPTR_(tertiary_handle); + pptr = getPPTR_(tertiary_handle); } assert (iterations <= 2); - // assert(check_validation_()); + //assert(check_validation_()); } - /* - * Resets the Interval_tree_impl to an empty state, but maintains a handle - * on the current intervals. - */ - void reset() { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new IllegalArgumentException("invalid call"); + private void reset_(boolean b_new_intervals, boolean b_envelopes_ref) { + if (b_new_intervals) { + m_b_envelopes_ref = false; + m_envelopes_ref = null; + + m_b_sort_intervals = true; + m_b_constructing = true; + m_b_construction_ended = false; + + if (m_end_indices_unique == null) + m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); + else + m_end_indices_unique.resize(0); + + if (!b_envelopes_ref) { + if (m_intervals == null) + m_intervals = new ArrayList(0); + else + m_intervals.clear(); + } else { + if (m_intervals != null) + m_intervals.clear(); + + m_b_envelopes_ref = true; + } + } else { + assert (m_b_offline_dynamic && m_b_construction_ended); + m_b_sort_intervals = false; + } + + if (m_b_offline_dynamic) { + if (m_interval_handles == null) { + m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); + m_secondary_treaps = new Treap(); + m_secondary_treaps.setComparator(new SecondaryComparator(this)); + } else { + m_secondary_treaps.clear(); + } + } else { + if (m_secondary_lists == null) + m_secondary_lists = new IndexMultiDCList(); + else + m_secondary_lists.clear(); + } + + if (m_tertiary_nodes == null) { + m_interval_nodes = new StridedIndexTypeCollection(3); + m_tertiary_nodes = new StridedIndexTypeCollection(m_b_offline_dynamic ? 5 : 4); + } else { + m_interval_nodes.deleteAll(false); + m_tertiary_nodes.deleteAll(false); + } - reset_(false); + m_root = -1; + m_c_count = 0; } - /** - * Returns the number of intervals stored in the Interval_tree_impl - */ - int size() { - return m_c_count; + private double getDiscriminant_(int tertiary_handle) { + int discriminant_index_1 = getDiscriminantIndex1_(tertiary_handle); + return getDiscriminantFromIndex1_(discriminant_index_1); } - /** - * Gets an iterator on the Interval_tree_impl using the input Envelope_1D - * interval as the query. To reuse the existing iterator on the same - * Interval_tree_impl but with a new query, use the reset_iterator function - * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval - * used for the query. \param tolerance The tolerance used for the - * intersection tests. - */ - IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, - tolerance); - } + private double getDiscriminantFromIndex1_(int discriminant_index_1) { + if (discriminant_index_1 == -1) + return NumberUtils.NaN(); - /** - * Gets an iterator on the Interval_tree_impl using the input double as the - * stabbing query. To reuse the existing iterator on the same - * Interval_tree_impl but with a new query, use the reset_iterator function - * on the Interval_tree_iterator_impl. \param query The double used for the - * stabbing query. \param tolerance The tolerance used for the intersection - * tests. - */ - IntervalTreeIteratorImpl getIterator(double query, double tolerance) { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, - tolerance); - } + if (discriminant_index_1 > 0) { + int j = discriminant_index_1 - 2; + int e_1 = m_end_indices_unique.get(j); + int e_2 = m_end_indices_unique.get(j + 1); - /** - * Gets an iterator on the Interval_tree_impl. - */ - IntervalTreeIteratorImpl getIterator() { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); - } + double v_1 = getValue_(e_1); + double v_2 = getValue_(e_2); + assert (v_1 < v_2); - private static final class SecondaryComparator extends Treap.Comparator { - SecondaryComparator(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; + return 0.5 * (v_1 + v_2); } - @Override - public int compare(Treap treap, int e_1, int node) { - int e_2 = treap.getElement(node); - double v_1 = m_interval_tree.getValue_(e_1); - double v_2 = m_interval_tree.getValue_(e_2); + int j = -discriminant_index_1 - 2; + assert (j >= 0); + int e = m_end_indices_unique.get(j); + double v = getValue_(e); - if (v_1 < v_2) - return -1; - if (v_1 == v_2) { - if (isLeft_(e_1) && isRight_(e_2)) - return -1; - if (isLeft_(e_2) && isRight_(e_1)) - return 1; - return 0; - } - return 1; + return v; + } + + private int calculateDiscriminantIndex1_(int il, int ir) { + int discriminant_index_1; + + if (il < ir) { + int im = il + (ir - il) / 2; + discriminant_index_1 = im + 2; // positive discriminant means use average of im and im + 1 + } else { + discriminant_index_1 = -(il + 2); // negative discriminant just means use il (-(il + 2) will never be -1) } + return discriminant_index_1; + } + + static final class IntervalTreeIteratorImpl { + private IntervalTreeImpl m_interval_tree; - }; + private Envelope1D m_query = new Envelope1D(); + private int m_tertiary_handle; + private int m_next_tertiary_handle; + private int m_forked_handle; + private int m_current_end_handle; + private int m_next_end_handle; + private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32(0); + private int m_function_index; + private int[] m_function_stack = new int[2]; - private boolean m_b_offline_dynamic; - private ArrayList m_intervals; - private StridedIndexTypeCollection m_primary_nodes; // 8 elements for - // offline dynamic case, - // 7 elements for static - // case - private StridedIndexTypeCollection m_interval_nodes; // 3 elements - private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic - // case - private IndexMultiDCList m_secondary_lists; // for static case - private Treap m_secondary_treaps; // for off-line dynamic case - private AttributeStreamOfInt32 m_end_indices_unique; // for both offline - // dynamic and - // static cases - private int m_c_count; - private int m_root; - private boolean m_b_sort_intervals; - private boolean m_b_constructing; - private boolean m_b_construction_ended; + private interface State { + static final int initialize = 0; + static final int pIn = 1; + static final int pL = 2; + static final int pR = 3; + static final int pT = 4; + static final int right = 5; + static final int left = 6; + static final int all = 7; + } - private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { - int size = m_intervals.size(); + private int getNext_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getNext(m_current_end_handle); - for (int i = 0; i < 2 * size; i++) - end_indices.add(i); + return m_interval_tree.m_secondary_treaps.getNext(m_current_end_handle); + } - sortEndIndices_(end_indices, 0, 2 * size); - } + private int getPrev_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getPrev(m_current_end_handle); - private void querySortedDuplicatesRemoved_( - AttributeStreamOfInt32 end_indices_sorted) { - // remove duplicates + return m_interval_tree.m_secondary_treaps.getPrev(m_current_end_handle); + } - double prev = NumberUtils.TheNaN; - for (int i = 0; i < end_indices_sorted.size(); i++) { - int e = end_indices_sorted.get(i); - double v = getValue_(e); + private int getCurrentEndIndex_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getData(m_current_end_handle); - if (v != prev) { - m_end_indices_unique.add(e); - prev = v; - } + return m_interval_tree.m_secondary_treaps.getElement(m_current_end_handle); } - } - private void insertIntervalsStatic_() { - int size = m_intervals.size(); + int next() { + if (!m_interval_tree.m_b_construction_ended) + throw new GeometryException("invalid call"); - assert (m_b_sort_intervals); + if (m_function_index < 0) + return -1; - // sort - AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32( - 0); - end_indices_sorted.reserve(2 * size); - querySortedEndPointIndices_(end_indices_sorted); + boolean b_searching = true; - // remove duplicates - m_end_indices_unique.reserve(2 * size); - m_end_indices_unique.resize(0); - querySortedDuplicatesRemoved_(end_indices_sorted); + while (b_searching) { + switch (m_function_stack[m_function_index]) { + case State.pIn: + b_searching = pIn_(); + break; + case State.pL: + b_searching = pL_(); + break; + case State.pR: + b_searching = pR_(); + break; + case State.pT: + b_searching = pT_(); + break; + case State.right: + b_searching = right_(); + break; + case State.left: + b_searching = left_(); + break; + case State.all: + b_searching = all_(); + break; + case State.initialize: + b_searching = initialize_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } - assert (m_primary_nodes.size() == 0); - m_interval_nodes.setCapacity(size); // one for each interval being - // inserted. each element contains a - // primary node, a left secondary - // node, and a right secondary node. - m_secondary_lists.reserveNodes(2 * size); // one for each end point of - // the original interval set - // (not the unique set) - - AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(size); - interval_handles.setRange(-1, 0, size); + if (m_current_end_handle != -1) + return getCurrentEndIndex_() >> 1; - m_root = createPrimaryNode_(); + return -1; + } - for (int i = 0; i < end_indices_sorted.size(); i++) { - int e = end_indices_sorted.get(i); - int interval_handle = interval_handles.get(e >> 1); + private boolean initialize_() { + m_tertiary_handle = -1; + m_next_tertiary_handle = -1; + m_forked_handle = -1; + m_current_end_handle = -1; - if (interval_handle != -1) {// insert the right end point - assert (isRight_(e)); - int secondary_handle = getSecondaryFromInterval_(interval_handle); - setRightEnd_(interval_handle, - m_secondary_lists.addElement(secondary_handle, e)); - } else {// insert the left end point - assert (isLeft_(e)); - interval_handle = insertIntervalEnd_(e, m_root); - interval_handles.set(e >> 1, interval_handle); + if (m_interval_tree.m_tertiary_nodes != null && m_interval_tree.m_tertiary_nodes.size() > 0) { + m_function_stack[0] = State.pIn; // overwrite initialize + m_next_tertiary_handle = m_interval_tree.m_root; + return true; } + + m_function_index = -1; + return false; } - assert (m_secondary_lists.getNodeCount() == 2 * size); - } + private boolean pIn_() { + m_tertiary_handle = m_next_tertiary_handle; - private int insertIntervalEnd_(int end_index, int root) { - assert (isLeft_(end_index)); - int primary_handle = root; - int tertiary_handle = root; - int ptr = root; - int secondary_handle; - int interval_handle = -1; - int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; - int index = end_index >> 1; - double discriminant_tertiary = NumberUtils.TheNaN; - double discriminant_ptr = NumberUtils.TheNaN; - boolean bSearching = true; + if (m_tertiary_handle == -1) { + m_function_index = -1; + m_current_end_handle = -1; + return false; + } - double min = getMin_(index); - double max = getMax_(index); + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - while (bSearching) { - if (il < ir) { - im = il + (ir - il) / 2; + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - if (getDiscriminantIndex1_(primary_handle) == -1) - setDiscriminantIndices_(primary_handle, - m_end_indices_unique.get(im), - m_end_indices_unique.get(im + 1)); - } else { - assert (il == ir); - assert (min == max); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; + } - if (getDiscriminantIndex1_(primary_handle) == -1) - setDiscriminantIndices_(primary_handle, - m_end_indices_unique.get(il), - m_end_indices_unique.get(il)); + return true; } - double discriminant = getDiscriminant_(primary_handle); - assert (!NumberUtils.isNaN(discriminant)); + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (max < discriminant) { - if (ptr != -1) { - if (ptr == primary_handle) { - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = getLPTR_(primary_handle); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; + } - if (ptr != -1) - discriminant_ptr = getDiscriminant_(ptr); - else - discriminant_ptr = NumberUtils.TheNaN; - } else if (discriminant_ptr > discriminant) { - if (discriminant < discriminant_tertiary) - setLPTR_(tertiary_handle, primary_handle); - else - setRPTR_(tertiary_handle, primary_handle); + return true; + } - setRPTR_(primary_handle, ptr); + assert (m_query.contains(discriminant)); - if (m_b_offline_dynamic) { - setPPTR_(primary_handle, tertiary_handle); - setPPTR_(ptr, primary_handle); - } + m_function_stack[m_function_index] = State.pL; // overwrite pIn + m_forked_handle = m_tertiary_handle; + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = -1; - discriminant_ptr = NumberUtils.TheNaN; + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - assert (getLPTR_(primary_handle) == -1); - assert (getRightPrimary_(primary_handle) != -1); - } - } + return true; + } + + private boolean pL_() { + m_tertiary_handle = m_next_tertiary_handle; + + if (m_tertiary_handle == -1) { + m_function_stack[m_function_index] = State.pR; // overwrite pL + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_forked_handle); + return true; + } + + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - int left_handle = getLeftPrimary_(primary_handle); + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (left_handle == -1) { - left_handle = createPrimaryNode_(); - setLeftPrimary_(primary_handle, left_handle); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; } - primary_handle = left_handle; - ir = im; + return true; + } - continue; + assert (m_query.contains(discriminant)); + + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; } - if (min > discriminant) { - if (ptr != -1) { - if (ptr == primary_handle) { - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = getRPTR_(primary_handle); + int rptr = m_interval_tree.getRPTR_(m_tertiary_handle); - if (ptr != -1) - discriminant_ptr = getDiscriminant_(ptr); - else - discriminant_ptr = NumberUtils.TheNaN; - } else if (discriminant_ptr < discriminant) { - if (discriminant < discriminant_tertiary) - setLPTR_(tertiary_handle, primary_handle); - else - setRPTR_(tertiary_handle, primary_handle); + if (rptr != -1) { + m_tertiary_stack.add(rptr); // we'll search this in the pT state + } - setLPTR_(primary_handle, ptr); + return true; + } - if (m_b_offline_dynamic) { - setPPTR_(primary_handle, tertiary_handle); - setPPTR_(ptr, primary_handle); - } + private boolean pR_() { + m_tertiary_handle = m_next_tertiary_handle; - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = -1; - discriminant_ptr = NumberUtils.TheNaN; + if (m_tertiary_handle == -1) { + m_function_stack[m_function_index] = State.pT; // overwrite pR + return true; + } - assert (getRPTR_(primary_handle) == -1); - assert (getLeftPrimary_(primary_handle) != -1); - } - } + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - int right_handle = getRightPrimary_(primary_handle); + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - if (right_handle == -1) { - right_handle = createPrimaryNode_(); - setRightPrimary_(primary_handle, right_handle); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; } - primary_handle = right_handle; - il = im + 1; - - continue; + return true; } - secondary_handle = getSecondaryFromPrimary(primary_handle); + assert (m_query.contains(discriminant)); - if (secondary_handle == -1) { - secondary_handle = createSecondary_(primary_handle); - setSecondaryToPrimary_(primary_handle, secondary_handle); - } + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - int left_end_handle = addEndIndex(secondary_handle, end_index); - interval_handle = createIntervalNode_(); - setSecondaryToInterval_(interval_handle, secondary_handle); - setLeftEnd_(interval_handle, left_end_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (primary_handle != ptr) { - assert (primary_handle != -1); - assert (getLPTR_(primary_handle) == -1 - && getRPTR_(primary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(primary_handle) == -1)); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - if (discriminant < discriminant_tertiary) - setLPTR_(tertiary_handle, primary_handle); - else - setRPTR_(tertiary_handle, primary_handle); + int lptr = m_interval_tree.getLPTR_(m_tertiary_handle); - if (m_b_offline_dynamic) - setPPTR_(primary_handle, tertiary_handle); + if (lptr != -1) { + m_tertiary_stack.add(lptr); // we'll search this in the pT state + } - if (ptr != -1) { - if (discriminant_ptr < discriminant) - setLPTR_(primary_handle, ptr); - else - setRPTR_(primary_handle, ptr); + return true; + } - if (m_b_offline_dynamic) - setPPTR_(ptr, primary_handle); - } + private boolean pT_() { + if (m_tertiary_stack.size() == 0) { + m_function_index = -1; + m_current_end_handle = -1; + return false; } - bSearching = false; - } + m_tertiary_handle = m_tertiary_stack.get(m_tertiary_stack.size() - 1); + m_tertiary_stack.resize(m_tertiary_stack.size() - 1); - return interval_handle; - } + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - private int createPrimaryNode_() { - return m_primary_nodes.newElement(); - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - private int createSecondary_(int primary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.createList(primary_handle); + if (m_interval_tree.getLPTR_(m_tertiary_handle) != -1) + m_tertiary_stack.add(m_interval_tree.getLPTR_(m_tertiary_handle)); - return m_secondary_treaps.createTreap(primary_handle); - } + if (m_interval_tree.getRPTR_(m_tertiary_handle) != -1) + m_tertiary_stack.add(m_interval_tree.getRPTR_(m_tertiary_handle)); - private int createIntervalNode_() { - return m_interval_nodes.newElement(); - } + return true; + } - private void reset_(boolean b_new_intervals) { - if (b_new_intervals) { - m_b_sort_intervals = true; - m_b_constructing = true; - m_b_construction_ended = false; + private boolean left_() { + m_current_end_handle = m_next_end_handle; - if (m_end_indices_unique == null) - m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(0)); - else - m_end_indices_unique.resize(0); + if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { + m_next_end_handle = getNext_(); + return false; + } - if (m_intervals == null) - m_intervals = new ArrayList(0); - else - m_intervals.clear(); - } else { - assert (m_b_offline_dynamic && m_b_construction_ended); - m_b_sort_intervals = false; + m_function_index--; + return true; } - if (m_b_offline_dynamic) { - if (m_interval_handles == null) { - m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(0)); - m_secondary_treaps = new Treap(); - m_secondary_treaps.setComparator(new SecondaryComparator(this)); - } else { - m_secondary_treaps.clear(); + private boolean right_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { + m_next_end_handle = getPrev_(); + return false; } - } else { - if (m_secondary_lists == null) - m_secondary_lists = new IndexMultiDCList(); - else - m_secondary_lists.clear(); - } - if (m_primary_nodes == null) { - m_interval_nodes = new StridedIndexTypeCollection(3); - m_primary_nodes = new StridedIndexTypeCollection( - m_b_offline_dynamic ? 8 : 7); - } else { - m_interval_nodes.deleteAll(false); - m_primary_nodes.deleteAll(false); + m_function_index--; + return true; } - m_root = -1; - m_c_count = 0; - } - - private void setDiscriminantIndices_(int primary_handle, int e_1, int e_2) { - setDiscriminantIndex1_(primary_handle, e_1); - setDiscriminantIndex2_(primary_handle, e_2); - } + private boolean all_() { + m_current_end_handle = m_next_end_handle; - private double getDiscriminant_(int primary_handle) { - int e_1 = getDiscriminantIndex1_(primary_handle); - if (e_1 == -1) - return NumberUtils.TheNaN; + if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { + m_next_end_handle = getNext_(); + return false; + } - int e_2 = getDiscriminantIndex2_(primary_handle); - assert (e_2 != -1); + m_function_index--; + return true; + } - double v_1 = getValue_(e_1); - double v_2 = getValue_(e_2); + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, Envelope1D query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } - if (v_1 == v_2) - return v_1; + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } - return 0.5 * (v_1 + v_2); - } + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + m_function_index = -1; + } - private boolean isActive_(int primary_handle) { - int secondary_handle = getSecondaryFromPrimary(primary_handle); + void resetIterator(Envelope1D query, double tolerance) { + m_query.vmin = query.vmin - tolerance; + m_query.vmax = query.vmax + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } - if (secondary_handle != -1) - return true; + void resetIterator(double query_min, double query_max, double tolerance) { + m_query.vmin = query_min - tolerance; + m_query.vmax = query_max + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } - int left_handle = getLeftPrimary_(primary_handle); + void resetIterator(double query, double tolerance) { + m_query.vmin = query - tolerance; + m_query.vmax = query + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + } - if (left_handle == -1) - return false; + private static final class SecondaryComparator extends Treap.Comparator { + SecondaryComparator(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } - int right_handle = getRightPrimary_(primary_handle); + @Override + public int compare(Treap treap, int e_1, int node) { + int e_2 = treap.getElement(node); + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); - if (right_handle == -1) - return false; + if (v_1 < v_2) + return -1; + if (v_1 == v_2) { + if (isLeft_(e_1) && isRight_(e_2)) + return -1; + if (isLeft_(e_2) && isRight_(e_1)) + return 1; + return 0; + } + return 1; + } - return true; + private IntervalTreeImpl m_interval_tree; } - private void setDiscriminantIndex1_(int primary_handle, int end_index) { - m_primary_nodes.setField(primary_handle, 0, end_index); + private int createTertiaryNode_(int discriminant_index_1) { + int tertiary_handle = m_tertiary_nodes.newElement(); + setDiscriminantIndex1_(tertiary_handle, discriminant_index_1); + return tertiary_handle; } - private void setDiscriminantIndex2_(int primary_handle, int end_index) { - m_primary_nodes.setField(primary_handle, 1, end_index); + private int createSecondary_(int tertiary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.createList(tertiary_handle); + + return m_secondary_treaps.createTreap(tertiary_handle); } - private void setLeftPrimary_(int primary_handle, int left_handle) { - m_primary_nodes.setField(primary_handle, 3, left_handle); + private int createIntervalNode_() { + return m_interval_nodes.newElement(); } - private void setRightPrimary_(int primary_handle, int right_handle) { - m_primary_nodes.setField(primary_handle, 4, right_handle); + private void setDiscriminantIndex1_(int tertiary_handle, int end_index) { + m_tertiary_nodes.setField(tertiary_handle, 0, end_index); } - private void setSecondaryToPrimary_(int primary_handle, int secondary_handle) { - m_primary_nodes.setField(primary_handle, 2, secondary_handle); + private void setSecondaryToTertiary_(int tertiary_handle, int secondary_handle) { + m_tertiary_nodes.setField(tertiary_handle, 1, secondary_handle); } - private void setLPTR_(int primary_handle, int lptr) { - m_primary_nodes.setField(primary_handle, 5, lptr); + private void setLPTR_(int tertiary_handle, int lptr) { + m_tertiary_nodes.setField(tertiary_handle, 2, lptr); } - private void setRPTR_(int primary_handle, int rptr) { - m_primary_nodes.setField(primary_handle, 6, rptr); + private void setRPTR_(int tertiary_handle, int rptr) { + m_tertiary_nodes.setField(tertiary_handle, 3, rptr); } - private void setPPTR_(int primary_handle, int pptr) { - m_primary_nodes.setField(primary_handle, 7, pptr); + private void setPPTR_(int tertiary_handle, int pptr) { + m_tertiary_nodes.setField(tertiary_handle, 4, pptr); } - private void setSecondaryToInterval_(int interval_handle, - int secondary_handle) { + private void setSecondaryToInterval_(int interval_handle, int secondary_handle) { m_interval_nodes.setField(interval_handle, 0, secondary_handle); } - private int addEndIndex(int secondary_handle, int end_index) { + private int addEndIndex_(int secondary_handle, int end_index) { int end_index_handle; if (!m_b_offline_dynamic) - end_index_handle = m_secondary_lists.addElement(secondary_handle, - end_index); + end_index_handle = m_secondary_lists.addElement(secondary_handle, end_index); else - end_index_handle = m_secondary_treaps.addElement(end_index, - secondary_handle); + end_index_handle = m_secondary_treaps.addElement(end_index, secondary_handle); return end_index_handle; } @@ -1124,58 +1092,24 @@ private void setRightEnd_(int interval_handle, int right_end_handle) { m_interval_nodes.setField(interval_handle, 2, right_end_handle); } - private int getFirst_(int secondary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.getFirst(secondary_handle); - - return m_secondary_treaps.getFirst(secondary_handle); - } - - private int getLast_(int secondary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.getLast(secondary_handle); - - return m_secondary_treaps.getLast(secondary_handle); - } - - private static boolean isLeft_(int end_index) { - return (end_index & 0x1) == 0; - } - - private static boolean isRight_(int end_index) { - return (end_index & 0x1) == 1; - } - - private int getDiscriminantIndex1_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 0); - } - - private int getDiscriminantIndex2_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 1); - } - - private int getSecondaryFromPrimary(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 2); - } - - private int getLeftPrimary_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 3); + private int getDiscriminantIndex1_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 0); } - private int getRightPrimary_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 4); + private int getSecondaryFromTertiary_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 1); } - private int getLPTR_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 5); + private int getLPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 2); } - private int getRPTR_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 6); + private int getRPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 3); } - private int getPPTR_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 7); + private int getPPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 4); } private int getSecondaryFromInterval_(int interval_handle) { @@ -1191,76 +1125,32 @@ private int getRightEnd_(int interval_handle) { } private double getMin_(int i) { - Envelope1D interval = m_intervals.get(i); - return interval.vmin; + return (!m_b_envelopes_ref ? m_intervals.get(i).vmin : m_envelopes_ref.get(i).xmin); } private double getMax_(int i) { - Envelope1D interval = m_intervals.get(i); - return interval.vmax; + return (!m_b_envelopes_ref ? m_intervals.get(i).vmax : m_envelopes_ref.get(i).xmax); } - // *********** Helpers for Bucket sort************** - private BucketSort m_bucket_sort; - - private void sortEndIndices_(AttributeStreamOfInt32 end_indices, - int begin_, int end_) { - if (m_bucket_sort == null) - m_bucket_sort = new BucketSort(); + private int getFirst_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getFirst(secondary_handle); - IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper( - this); - m_bucket_sort.sort(end_indices, begin_, end_, sorter); + return m_secondary_treaps.getFirst(secondary_handle); } - private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, - int begin_, int end_) { - end_indices.Sort(begin_, end_, new EndPointsComparer(this)); - } + private int getLast_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getLast(secondary_handle); - private double getValue_(int e) { - Envelope1D interval = m_intervals.get(e >> 1); - double v = (isLeft_(e) ? interval.vmin : interval.vmax); - return v; + return m_secondary_treaps.getLast(secondary_handle); } - private static final class EndPointsComparer extends - AttributeStreamOfInt32.IntComparator { // For user sort - EndPointsComparer(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public int compare(int e_1, int e_2) { - double v_1 = m_interval_tree.getValue_(e_1); - double v_2 = m_interval_tree.getValue_(e_2); - - if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) - return -1; - - return 1; - } - - private IntervalTreeImpl m_interval_tree; + private static boolean isLeft_(int end_index) { + return (end_index & 0x1) == 0; } - private class IntervalTreeBucketSortHelper extends ClassicSort { // For - // bucket - // sort - IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - m_interval_tree.sortEndIndicesHelper_(indices, begin, end); - } - - @Override - public double getValue(int e) { - return m_interval_tree.getValue_(e); - } - - private IntervalTreeImpl m_interval_tree; + private static boolean isRight_(int end_index) { + return (end_index & 0x1) == 1; } } diff --git a/src/main/java/com/esri/core/geometry/JsonGeometryException.java b/src/main/java/com/esri/core/geometry/JsonGeometryException.java new file mode 100644 index 00000000..a5552901 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/JsonGeometryException.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * A runtime exception raised when a JSON related exception occurs. + */ +public class JsonGeometryException extends GeometryException { + + /** + * Constructs a Json Geometry Exception with the given error string/message. + * + * @param str + * - The error string. + */ + public JsonGeometryException(String str) { + super(str); + } +} diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index 80666579..bf382dd9 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -24,10 +24,15 @@ package com.esri.core.geometry; import java.util.ArrayList; + import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; +import org.codehaus.jackson.JsonParseException; + +import java.io.IOException; final class JsonParserReader extends JsonReader { @@ -38,33 +43,33 @@ final class JsonParserReader extends JsonReader { } @Override - JsonToken nextToken() throws Exception { + JsonToken nextToken() throws JSONException, JsonParseException, IOException { JsonToken token = m_jsonParser.nextToken(); return token; } @Override - JsonToken currentToken() throws Exception { + JsonToken currentToken() throws JSONException, JsonParseException, IOException { return m_jsonParser.getCurrentToken(); } @Override - void skipChildren() throws Exception { + void skipChildren() throws JSONException, JsonParseException, IOException { m_jsonParser.skipChildren(); } @Override - String currentString() throws Exception { + String currentString() throws JSONException, JsonParseException, IOException { return m_jsonParser.getText(); } @Override - double currentDoubleValue() throws Exception { + double currentDoubleValue() throws JSONException, JsonParseException, IOException { return m_jsonParser.getValueAsDouble(); } @Override - int currentIntValue() throws Exception { + int currentIntValue() throws JSONException, JsonParseException, IOException { return m_jsonParser.getValueAsInt(); } } diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index ba3b8cca..b1f69d95 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -24,24 +24,28 @@ package com.esri.core.geometry; import java.util.ArrayList; + import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; +import org.codehaus.jackson.JsonParseException; +import java.io.IOException; abstract class JsonReader { - abstract JsonToken nextToken() throws Exception; + abstract JsonToken nextToken() throws JSONException, JsonParseException, IOException; - abstract JsonToken currentToken() throws Exception; + abstract JsonToken currentToken() throws JSONException, JsonParseException, IOException; - abstract void skipChildren() throws Exception; + abstract void skipChildren() throws JSONException, JsonParseException, IOException; - abstract String currentString() throws Exception; + abstract String currentString() throws JSONException, JsonParseException, IOException; - abstract double currentDoubleValue() throws Exception; + abstract double currentDoubleValue() throws JSONException, JsonParseException, IOException; - abstract int currentIntValue() throws Exception; + abstract int currentIntValue() throws JSONException, JsonParseException, IOException; } diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index f324a197..a8df0930 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -33,14 +33,14 @@ Object getJson() { @Override void startObject() { - next_(Action.addContainer); + next_(Action.addObject); m_jsonString.append('{'); m_functionStack.add(State.objectStart); } @Override void startArray() { - next_(Action.addContainer); + next_(Action.addArray); m_jsonString.append('['); m_functionStack.add(State.arrayStart); } @@ -57,6 +57,12 @@ void endArray() { m_jsonString.append(']'); } + @Override + void addFieldName(String fieldName) { + next_(Action.addKey); + appendQuote_(fieldName); + } + @Override void addPairObject(String fieldName) { next_(Action.addPair); @@ -90,11 +96,11 @@ void addPairDouble(String fieldName, double v) { } @Override - void addPairDoubleF(String fieldName, double v, int decimals) { + void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint) { next_(Action.addPair); appendQuote_(fieldName); m_jsonString.append(":"); - addValueDoubleF_(v, decimals); + addValueDouble_(v, precision, bFixedPoint); } @Override @@ -123,13 +129,13 @@ void addPairNull(String fieldName) { @Override void addValueObject() { - next_(Action.addContainer); + next_(Action.addObject); addValueObject_(); } @Override void addValueArray() { - next_(Action.addContainer); + next_(Action.addArray); addValueArray_(); } @@ -146,9 +152,9 @@ void addValueDouble(double v) { } @Override - void addValueDoubleF(double v, int decimals) { + void addValueDouble(double v, int precision, boolean bFixedPoint) { next_(Action.addTerminal); - addValueDoubleF_(v, decimals); + addValueDouble_(v, precision, bFixedPoint); } @Override @@ -202,13 +208,16 @@ private void addValueDouble_(double v) { StringUtils.appendDouble(v, 17, m_jsonString); } - private void addValueDoubleF_(double v, int decimals) { + private void addValueDouble_(double v, int precision, boolean bFixedPoint) { if (NumberUtils.isNaN(v)) { addValueNull_(); return; } - StringUtils.appendDoubleF(v, decimals, m_jsonString); + if (bFixedPoint) + StringUtils.appendDoubleF(v, precision, m_jsonString); + else + StringUtils.appendDouble(v, precision, m_jsonString); } private void addValueInt_(int v) { @@ -247,6 +256,9 @@ private void next_(int action) { case State.elementEnd: elementEnd_(action); break; + case State.fieldNameEnd: + fieldNameEnd_(action); + break; default: throw new GeometryException("internal error"); } @@ -259,7 +271,7 @@ private void accept_(int action) { } private void start_(int action) { - if (action == Action.addContainer) { + if ((action & Action.addContainer) != 0) { m_functionStack.removeLast(); } else { throw new GeometryException("invalid call"); @@ -267,18 +279,25 @@ private void start_(int action) { } private void objectStart_(int action) { + if (action != Action.popObject && action != Action.addPair && action != Action.addKey) + throw new GeometryException("invalid call"); + m_functionStack.removeLast(); if (action == Action.addPair) { m_functionStack.add(State.pairEnd); - } else if (action != Action.popObject) { - throw new GeometryException("invalid call"); + } else if (action == Action.addKey) { + m_functionStack.add(State.pairEnd); + m_functionStack.add(State.fieldNameEnd); } } private void pairEnd_(int action) { if (action == Action.addPair) { m_jsonString.append(','); + } else if (action == Action.addKey) { + m_jsonString.append(','); + m_functionStack.add(State.fieldNameEnd); } else if (action == Action.popObject) { m_functionStack.removeLast(); } else { @@ -287,12 +306,13 @@ private void pairEnd_(int action) { } private void arrayStart_(int action) { + if ((action & Action.addValue) == 0 && action != Action.popArray) + throw new GeometryException("invalid call"); + m_functionStack.removeLast(); if ((action & Action.addValue) != 0) { m_functionStack.add(State.elementEnd); - } else if (action != Action.popArray) { - throw new GeometryException("invalid call"); } } @@ -306,6 +326,14 @@ private void elementEnd_(int action) { } } + private void fieldNameEnd_(int action) { + if ((action & Action.addValue) == 0) + throw new GeometryException("invalid call"); + + m_functionStack.removeLast(); + m_jsonString.append(':'); + } + private void appendQuote_(String string) { int count = 0; int start = 0; diff --git a/src/main/java/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java index d7c4b639..91028624 100644 --- a/src/main/java/com/esri/core/geometry/JsonValueReader.java +++ b/src/main/java/com/esri/core/geometry/JsonValueReader.java @@ -24,10 +24,15 @@ package com.esri.core.geometry; import java.util.ArrayList; + import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; +import org.codehaus.jackson.JsonParseException; + +import java.io.IOException; final class JsonValueReader extends JsonReader { @@ -108,7 +113,7 @@ Object currentObject_() { } @Override - JsonToken nextToken() throws Exception { + JsonToken nextToken() throws JSONException, JsonParseException { if (m_parentStack.isEmpty()) { m_currentToken = JsonToken.NOT_AVAILABLE; return m_currentToken; @@ -170,12 +175,12 @@ JsonToken nextToken() throws Exception { } @Override - JsonToken currentToken() throws Exception { + JsonToken currentToken() throws JSONException, JsonParseException, IOException { return m_currentToken; } @Override - void skipChildren() throws Exception { + void skipChildren() throws JSONException, JsonParseException, IOException { assert (!m_parentStack.isEmpty()); if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { @@ -196,7 +201,7 @@ void skipChildren() throws Exception { } @Override - String currentString() throws Exception { + String currentString() throws JSONException, JsonParseException, IOException { if (m_currentToken == JsonToken.FIELD_NAME) { return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); } @@ -209,7 +214,7 @@ String currentString() throws Exception { } @Override - double currentDoubleValue() throws Exception { + double currentDoubleValue() throws JSONException, JsonParseException, IOException { if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { throw new GeometryException("invalid call"); } @@ -218,7 +223,7 @@ String currentString() throws Exception { } @Override - int currentIntValue() throws Exception { + int currentIntValue() throws JSONException, JsonParseException, IOException { if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { throw new GeometryException("invalid call"); } diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 0baa0f2b..095f50e1 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -35,6 +35,8 @@ abstract class JsonWriter { abstract void endArray(); + abstract void addFieldName(String fieldName); + abstract void addPairObject(String fieldName); abstract void addPairArray(String fieldName); @@ -43,7 +45,7 @@ abstract class JsonWriter { abstract void addPairDouble(String fieldName, double v); - abstract void addPairDoubleF(String fieldName, double v, int decimals); + abstract void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint); abstract void addPairInt(String fieldName, int v); @@ -59,7 +61,7 @@ abstract class JsonWriter { abstract void addValueDouble(double v); - abstract void addValueDoubleF(double v, int decimals); + abstract void addValueDouble(double v, int precision, boolean bFixedPoint); abstract void addValueInt(int v); @@ -70,11 +72,14 @@ abstract class JsonWriter { protected interface Action { static final int accept = 0; - static final int addContainer = 1; + static final int addObject = 1; + static final int addArray = 2; static final int popObject = 4; static final int popArray = 8; - static final int addPair = 16; + static final int addKey = 16; static final int addTerminal = 32; + static final int addPair = 64; + static final int addContainer = addObject | addArray; static final int addValue = addContainer | addTerminal; } @@ -85,6 +90,7 @@ protected interface State { static final int objectStart = 2; static final int arrayStart = 3; static final int pairEnd = 4; - static final int elementEnd = 6; + static final int elementEnd = 5; + static final int fieldNameEnd = 6; } } diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index a3c5e570..4eccd513 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -35,10 +35,6 @@ */ public final class Line extends Segment implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use - // writeReplace and - // GeometrySerializer - @Override public Geometry.Type getType() { return Type.Line; @@ -92,7 +88,7 @@ public Line() { m_description = vd; } - Line(double x1, double y1, double x2, double y2) { + public Line(double x1, double y1, double x2, double y2) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setStartXY(x1, y1); setEndXY(x2, y2); @@ -199,7 +195,7 @@ public Geometry createInstance() { } @Override - void getCoord2D(double t, Point2D pt) { + public void getCoord2D(double t, Point2D pt) { // We want: // 1. When t == 0, get exactly Start // 2. When t == 1, get exactly End @@ -209,7 +205,7 @@ void getCoord2D(double t, Point2D pt) { } @Override - Segment cut(double t1, double t2) { + public Segment cut(double t1, double t2) { SegmentBuffer segmentBuffer = new SegmentBuffer(); cut(t1, t2, segmentBuffer); return segmentBuffer.get(); @@ -270,7 +266,7 @@ public double getAttributeAsDbl(double t, int semantics, int ordinate) { } @Override - double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { + public double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { double vx = m_xEnd - m_xStart; double vy = m_yEnd - m_yStart; double v2 = vx * vx + vy * vy; @@ -390,7 +386,7 @@ boolean _isIntersectingPoint(Point2D pt, double tolerance, * given tolerance. */ @Override - boolean isIntersecting(Point2D pt, double tolerance) { + public boolean isIntersecting(Point2D pt, double tolerance) { return _isIntersectingPoint(pt, tolerance, false); } diff --git a/src/main/java/com/esri/core/geometry/LnSrlzr.java b/src/main/java/com/esri/core/geometry/LnSrlzr.java new file mode 100644 index 00000000..b1bd001f --- /dev/null +++ b/src/main/java/com/esri/core/geometry/LnSrlzr.java @@ -0,0 +1,93 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for Lin +public class LnSrlzr implements Serializable { + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; + + public Object readResolve() throws ObjectStreamException { + Line ln = null; + if (descriptionBitMask == -1) + return null; + + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + ln = new Line(vd); + if (attribs != null) { + ln.setStartXY(attribs[0], attribs[1]); + ln.setEndXY(attribs[2], attribs[3]); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + ln.setStartAttribute(semantics, ord, attribs[index++]); + ln.setEndAttribute(semantics, ord, attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + + return ln; + } + + public void setGeometryByValue(Line ln) throws ObjectStreamException { + try { + attribs = null; + if (ln == null) { + descriptionBitMask = -1; + } + + VertexDescription vd = ln.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + + attribs = new double[vd.getTotalComponentCount() * 2]; + attribs[0] = ln.getStartX(); + attribs[1] = ln.getStartY(); + attribs[2] = ln.getEndX(); + attribs[3] = ln.getEndY(); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + attribs[index++] = ln.getStartAttributeAsDbl(semantics, ord); + attribs[index++] = ln.getEndAttributeAsDbl(semantics, ord); + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index 208fe4ba..92d5d8dd 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -135,11 +135,21 @@ static int sign(double value) { return value < 0 ? -1 : (value > 0) ? 1 : 0; } + /** + * Rounds towards zero. + */ + static double truncate(double v) { + if (v >= 0) + return Math.floor(v); + else + return -Math.floor(-v); + } + /** * C fmod function. */ static double FMod(double x, double y) { - return x - Math.floor(x / y) * y; + return x - truncate(x / y) * y; } @@ -184,22 +194,26 @@ static double lerp(double start_, double end_, double t) { *It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. */ static void lerp(Point2D start_, Point2D end_, double t, Point2D result) { + assert(start_ != result); // When end == start, we want result to be equal to start, for all t // values. At the same time, when end != start, we want the result to be // equal to start for t==0 and end for t == 1.0 // The regular formula end_ * t + (1.0 - t) * start_, when end_ == // start_, and t at 1/3, produces value different from start + double rx, ry; if (t <= 0.5) { - result.x = start_.x + (end_.x - start_.x) * t; - result.y = start_.y + (end_.y - start_.y) * t; + rx = start_.x + (end_.x - start_.x) * t; + ry = start_.y + (end_.y - start_.y) * t; } else { - result.x = end_.x - (end_.x - start_.x) * (1.0 - t); - result.y = end_.y - (end_.y - start_.y) * (1.0 - t); + rx = end_.x - (end_.x - start_.x) * (1.0 - t); + ry = end_.y - (end_.y - start_.y) * (1.0 - t); } - assert (t < 0 || t > 1.0 || (result.x >= start_.x && result.x <= end_.x) || (result.x <= start_.x && result.x >= end_.x)); - assert (t < 0 || t > 1.0 || (result.y >= start_.y && result.y <= end_.y) || (result.y <= start_.y && result.y >= end_.y)); + assert (t < 0 || t > 1.0 || (rx >= start_.x && rx <= end_.x) || (rx <= start_.x && rx >= end_.x)); + assert (t < 0 || t > 1.0 || (ry >= start_.y && ry <= end_.y) || (ry <= start_.y && ry >= end_.y)); + result.x = rx; + result.y = ry; } static void lerp(double start_x, double start_y, double end_x, double end_y, double t, Point2D result) { diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 8e778725..48cf8bd9 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -39,12 +39,12 @@ public VertexDescription getDescription() { } @Override - void assignVertexDescription(VertexDescription src) { + public void assignVertexDescription(VertexDescription src) { m_impl.assignVertexDescription(src); } @Override - void mergeVertexDescription(VertexDescription src) { + public void mergeVertexDescription(VertexDescription src) { m_impl.mergeVertexDescription(src); } @@ -277,6 +277,41 @@ void addPath(Point2D[] points, int count, boolean bForward) { m_impl.addPath(points, count, bForward); } + /** + * Adds segments from a source multipath to this MultiPath. + * + * @param src + * The source MultiPath to add segments from. + * @param srcPathIndex + * The index of the path in the the source MultiPath. + * @param srcSegmentFrom + * The index of first segment in the path to start adding from. + * The value has to be between 0 and + * src.getSegmentCount(srcPathIndex) - 1. + * @param srcSegmentCount + * The number of segments to add. If 0, the function does + * nothing. + * @param bStartNewPath + * When true, a new path is added and segments are added to it. + * Otherwise the segments are added to the last path of this + * MultiPath. + * + * If bStartNewPath false, the first point of the first source + * segment is not added. This is done to ensure proper connection + * to existing segments. When the source path is closed, and the + * closing segment is among those to be added, it is added also + * as a closing segment, not as a real segment. Use add_segment + * instead if you do not like that behavior. + * + * This MultiPath obtains all missing attributes from the src + * MultiPath. + */ + public void addSegmentsFromPath(MultiPath src, int srcPathIndex, + int srcSegmentFrom, int srcSegmentCount, boolean bStartNewPath) { + m_impl.addSegmentsFromPath((MultiPathImpl) src._getImpl(), + srcPathIndex, srcSegmentFrom, srcSegmentCount, bStartNewPath); + } + /** * Adds a new segment to this multipath. * @@ -724,12 +759,12 @@ public int hashCode() { } @Override - void getPointByVal(int index, Point outPoint) { + public void getPointByVal(int index, Point outPoint) { m_impl.getPointByVal(index, outPoint); } @Override - void setPointByVal(int index, Point point) { + public void setPointByVal(int index, Point point) { m_impl.setPointByVal(index, point); } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 43ed98a5..f6d606bb 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -1923,18 +1923,27 @@ public boolean equals(Object other) { if (pathCount != pathCountOther) return false; - if (m_paths != null + if (pathCount > 0 && m_paths != null && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) return false; - - if (m_fill_rule != otherMultiPath.m_fill_rule) - return false; - if (m_pathFlags != null - && !m_pathFlags - .equals(otherMultiPath.m_pathFlags, 0, pathCount)) + if (m_fill_rule != otherMultiPath.m_fill_rule) return false; + { + // Note: OGC flags do not participate in the equals operation by + // design. + // Because for the polygon pathFlags will have all enum_closed set, + // we do not need to compare this stream. Only for polyline. + // Polyline does not have OGC flags set. + if (!m_bPolygon) { + if (m_pathFlags != null + && !m_pathFlags.equals(otherMultiPath.m_pathFlags, 0, + pathCount)) + return false; + } + } + return super.equals(other); } @@ -2101,7 +2110,7 @@ protected void _updateOGCFlags() { _updateRingAreas2D(); int pathCount = getPathCount(); - if (m_pathFlags == null || m_pathFlags.size() < pathCount) + if (pathCount > 0 && (m_pathFlags == null || m_pathFlags.size() < pathCount)) m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase .createByteStream(pathCount + 1); diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 2d2cbf28..8a3f6f6a 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -32,7 +32,7 @@ * information where the order and individual identity of each point is not an * essential characteristic of the point set. */ -public final class MultiPoint extends MultiVertexGeometry implements +public class MultiPoint extends MultiVertexGeometry implements Serializable { private static final long serialVersionUID = 2L; @@ -122,6 +122,15 @@ public void add(double x, double y) { m_impl.add(x, y); } + /** + * Adds a point with the specified X, Y coordinates to this multipoint. + * + * @param pt the point to add + */ + public void add(Point2D pt) { + m_impl.add(pt.x, pt.y); + } + /** * Adds a 3DPoint with the specified X, Y, Z coordinates to this multipoint. * @@ -265,7 +274,7 @@ public void addAttribute(int semantics) { } @Override - void assignVertexDescription(VertexDescription src) { + public void assignVertexDescription(VertexDescription src) { m_impl.assignVertexDescription(src); } @@ -280,7 +289,7 @@ public void dropAttribute(int semantics) { } @Override - void mergeVertexDescription(VertexDescription src) { + public void mergeVertexDescription(VertexDescription src) { m_impl.mergeVertexDescription(src); } @@ -346,12 +355,12 @@ int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, } @Override - void getPointByVal(int index, Point outPoint) { + public void getPointByVal(int index, Point outPoint) { m_impl.getPointByVal(index, outPoint); } @Override - void setPointByVal(int index, Point pointSrc) { + public void setPointByVal(int index, Point pointSrc) { m_impl.setPointByVal(index, pointSrc); } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java index 668d6b33..c164b48c 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java @@ -197,7 +197,7 @@ abstract void setAttribute(int semantics, int index, int ordinate, * Returns given vertex of the Geometry. The outPoint will have same * VertexDescription as this Geometry. */ - abstract void getPointByVal(int index, Point outPoint); + public abstract void getPointByVal(int index, Point outPoint); /** * Sets the vertex at given index of the Geometry. @@ -214,6 +214,6 @@ abstract void setAttribute(int semantics, int index, int ordinate, * the Geometry will be set to the default values (see * VertexDescription::GetDefaultValue). */ - abstract void setPointByVal(int index, Point pointSrc); + public abstract void setPointByVal(int index, Point pointSrc); } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 11ef7ed4..6e2694c7 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -180,9 +180,8 @@ public MultiVertexGeometryImpl() { m_accelerators = null; } - // Checked vs. Jan 11, 2011 @Override - void getPointByVal(int index, Point dst) { + public void getPointByVal(int index, Point dst) { if (index < 0 || index >= m_pointCount) // TODO throw new GeometryException("index out of bounds"); @@ -212,9 +211,8 @@ void getPointByVal(int index, Point dst) { } } - // Checked vs. Jan 11, 2011 @Override - protected void setPointByVal(int index, Point src) { + public void setPointByVal(int index, Point src) { if (index < 0 || index >= m_pointCount) throw new GeometryException("index out of bounds"); @@ -1110,6 +1108,9 @@ public abstract boolean _buildRasterizedGeometryAccelerator( public abstract boolean _buildQuadTreeAccelerator( GeometryAccelerationDegree d); - // //////////////////METHODS To REMOVE /////////////////////// + @Override + public String toString() { + return "MultiVertexGeometryImpl"; + } } diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 209df086..4ee00a1e 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -68,6 +68,12 @@ static double NaN() { return Double.NaN; } + //combines two hash values + public static int hashCombine(int hash1, int hash2) { + return (hash1 * 31 + hash2) & 0x7FFFFFFF; + } + + //makes a hash out of an int static int hash(int n) { int hash = 5381; hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ @@ -78,12 +84,14 @@ static int hash(int n) { return hash; } + // //makes a hash out of an double static int hash(double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); return hash(hc); } + //adds an int to a hash value static int hash(int hashIn, int n) { int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); @@ -93,6 +101,7 @@ static int hash(int hashIn, int n) { return hash; } + //adds a double to a hash value static int hash(int hash, double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); diff --git a/src/main/java/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java index cca44f54..93c71b02 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorBuffer.java @@ -58,6 +58,31 @@ public abstract Geometry execute(Geometry inputGeometry, SpatialReference sr, double distance, ProgressTracker progressTracker); + /** + *Creates a buffer around the input geometries + * + *@param input_geometries The geometries to buffer. + *@param sr The Spatial_reference of the Geometries. It is used to obtain the tolerance. Can be null. + *@param distances The buffer distances for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value is used for the rest of geometries. + *@param max_deviation The max deviation of the result buffer from the true buffer in the units of the sr. + *When max_deviation is NaN or 0, it is replaced with 1e-5 * abs(distance). + *When max_deviation is larger than MIN = 0.5 * abs(distance), it is replaced with MIN. See below for more information. + *@param max_vertices_in_full_circle The maximum number of vertices in polygon produced from a buffered point. A value of 96 is used in methods that do not accept max_vertices_in_full_circle. + *If the value is less than MIN=12, it is set to MIN. See below for more information. + *@param b_union If True, the buffered geometries will be unioned, otherwise they wont be unioned. + *@param progress_tracker The progress tracker that allows to cancel the operation. Pass null if not needed. + * + *The max_deviation and max_vertices_in_full_circle control the quality of round joins in the buffer. That is, the precision of the buffer is max_deviation unless + *the number of required vertices is too large. + *The max_vertices_in_full_circle controls how many vertices can be in each round join in the buffer. It is approximately equal to the number of vertices in the polygon around a + *buffered point. It has a priority over max_deviation. The max deviation is the distance from the result polygon to a true buffer. + *The real deviation is calculated as the max(max_deviation, abs(distance) * (1 - cos(PI / max_vertex_in_complete_circle))). + * + *Note that max_deviation can be exceeded because geometry is generalized with 0.25 * real_deviation, also input segments closer than 0.25 * real_deviation are + *snapped to a point. + */ + abstract GeometryCursor execute(GeometryCursor input_geometries, SpatialReference sr, double[] distances, double max_deviation, int max_vertices_in_full_circle, boolean b_union, ProgressTracker progress_tracker); + public static OperatorBuffer local() { return (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator( Type.Buffer); diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java index 397ec89c..dd5716ca 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java @@ -25,25 +25,29 @@ package com.esri.core.geometry; class OperatorBufferCursor extends GeometryCursor { - + Bufferer m_bufferer = new Bufferer(); private GeometryCursor m_inputGeoms; private SpatialReferenceImpl m_Spatial_reference; private ProgressTracker m_progress_tracker; private double[] m_distances; private Envelope2D m_currentUnionEnvelope2D; - private boolean m_bUnion; - + double m_max_deviation; + int m_max_vertices_in_full_circle; private int m_index; private int m_dindex; OperatorBufferCursor(GeometryCursor inputGeoms, SpatialReference sr, - double[] distances, boolean b_union, + double[] distances, + double max_deviation, + int max_vertices, + boolean b_union, ProgressTracker progress_tracker) { m_index = -1; m_inputGeoms = inputGeoms; + m_max_deviation = max_deviation; + m_max_vertices_in_full_circle = max_vertices; m_Spatial_reference = (SpatialReferenceImpl) (sr); m_distances = distances; - m_bUnion = b_union; m_currentUnionEnvelope2D = new Envelope2D(); m_currentUnionEnvelope2D.setEmpty(); m_dindex = -1; @@ -72,7 +76,7 @@ public int getGeometryID() { // virtual bool IsRecycling() OVERRIDE { return false; } Geometry buffer(Geometry geom, double distance) { - return Bufferer.buffer(geom, distance, m_Spatial_reference, - NumberUtils.TheNaN, 96, m_progress_tracker); + return m_bufferer.buffer(geom, distance, m_Spatial_reference, + m_max_deviation, m_max_vertices_in_full_circle, m_progress_tracker); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java index 5e195d57..8c44225f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java @@ -30,15 +30,8 @@ class OperatorBufferLocal extends OperatorBuffer { public GeometryCursor execute(GeometryCursor inputGeometries, SpatialReference sr, double[] distances, boolean bUnion, ProgressTracker progressTracker) { - if (bUnion) { - OperatorBufferCursor cursor = new OperatorBufferCursor( - inputGeometries, sr, distances, false, progressTracker); - return ((OperatorUnion) OperatorFactoryLocal.getInstance() - .getOperator(Operator.Type.Union)).execute(cursor, sr, - progressTracker); - } else - return new OperatorBufferCursor(inputGeometries, sr, distances, - false, progressTracker); + return execute(inputGeometries, sr, distances, NumberUtils.NaN(), 96, + bUnion, progressTracker); } @Override @@ -54,4 +47,20 @@ public Geometry execute(Geometry inputGeometry, SpatialReference sr, return outputCursor.next(); } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, double[] distances, double max_deviation, + int max_vertices_in_full_circle, boolean b_union, + ProgressTracker progressTracker) { + if (b_union) { + OperatorBufferCursor cursor = new OperatorBufferCursor( + inputGeometries, sr, distances, max_deviation, + max_vertices_in_full_circle, false, progressTracker); + return OperatorUnion.local().execute(cursor, sr, progressTracker);// (int)Operator_union::Options::enum_disable_edge_dissolver + } else { + return new OperatorBufferCursor(inputGeometries, sr, distances, + max_deviation, max_vertices_in_full_circle, false, + progressTracker); + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java index 11b32738..c90c9d72 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java @@ -30,9 +30,8 @@ class OperatorConvexHullCursor extends GeometryCursor { private GeometryCursor m_inputGeometryCursor; private int m_index; ConvexHull m_hull = new ConvexHull(); - - OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, - ProgressTracker progress_tracker) { + + OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, ProgressTracker progress_tracker) { m_index = -1; if (geoms == null) throw new IllegalArgumentException(); @@ -47,8 +46,7 @@ class OperatorConvexHullCursor extends GeometryCursor { public Geometry next() { if (m_b_merge) { if (!m_b_done) { - Geometry result = calculateConvexHullMerging_( - m_inputGeometryCursor, m_progress_tracker); + Geometry result = calculateConvexHullMerging_(m_inputGeometryCursor, m_progress_tracker); m_b_done = true; return result; } @@ -74,8 +72,7 @@ public int getGeometryID() { return m_index; } - private Geometry calculateConvexHullMerging_(GeometryCursor geoms, - ProgressTracker progress_tracker) { + private Geometry calculateConvexHullMerging_(GeometryCursor geoms, ProgressTracker progress_tracker) { Geometry geometry; while ((geometry = geoms.next()) != null) @@ -83,20 +80,19 @@ private Geometry calculateConvexHullMerging_(GeometryCursor geoms, return m_hull.getBoundingGeometry(); } - + @Override public boolean tock() { if (m_b_done) return true; - - if (!m_b_merge) - { + + if (!m_b_merge) { //Do not use tick/tock with the non-merging convex hull. //Call tick/next instead, //because tick pushes geometry into the cursor, and next performs a single convex hull on it. throw new GeometryException("Invalid call for non merging convex hull."); } - + Geometry geometry = m_inputGeometryCursor.next(); if (geometry != null) { m_hull.addGeometry(geometry); @@ -106,33 +102,64 @@ public boolean tock() { } } - static Geometry calculateConvexHull_(Geometry geom, - ProgressTracker progress_tracker) { - if (isConvex_(geom, progress_tracker)) - return geom; - - int type = geom.getType().value(); + static Geometry calculateConvexHull_(Geometry geom, ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return geom.createInstance(); - if (MultiPath.isSegment(type)) { - Polyline polyline = new Polyline(geom.getDescription()); - polyline.addSegment((Segment) geom, true); - return polyline; - } + Geometry.Type type = geom.getType(); - if (type == Geometry.GeometryType.MultiPoint) { - MultiPoint multi_point = (MultiPoint) geom; - if (multi_point.getPointCount() == 2) { + if (Geometry.isSegment(type.value())) {// Segments are always returned either as a Point or Polyline + Segment segment = (Segment) geom; + if (segment.getStartXY().equals(segment.getEndXY())) { + Point point = new Point(); + segment.queryStart(point); + return point; + } else { + Point pt = new Point(); + Polyline polyline = new Polyline(geom.getDescription()); + segment.queryStart(pt); + polyline.startPath(pt); + segment.queryEnd(pt); + polyline.lineTo(pt); + return polyline; + } + } else if (type == Geometry.Type.Envelope) { + Envelope envelope = (Envelope) geom; + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + if (env.xmin == env.xmax && env.ymin == env.ymax) { + Point point = new Point(); + envelope.queryCornerByVal(0, point); + return point; + } else if (env.xmin == env.xmax || env.ymin == env.ymax) { Point pt = new Point(); Polyline polyline = new Polyline(geom.getDescription()); - multi_point.getPointByVal(0, pt); + envelope.queryCornerByVal(0, pt); polyline.startPath(pt); - multi_point.getPointByVal(1, pt); + envelope.queryCornerByVal(1, pt); polyline.lineTo(pt); return polyline; + } else { + Polygon polygon = new Polygon(geom.getDescription()); + polygon.addEnvelope(envelope, false); + return polygon; } } - Polygon convex_hull = ConvexHull.construct((MultiVertexGeometry) geom); + if (isConvex_(geom, progress_tracker)) { + if (type == Geometry.Type.MultiPoint) {// Downgrade to a Point for simplistic output + MultiPoint multi_point = (MultiPoint) geom; + Point point = new Point(); + multi_point.getPointByVal(0, point); + return point; + } + + return geom; + } + + assert (Geometry.isMultiVertex(type.value())); + + Geometry convex_hull = ConvexHull.construct((MultiVertexGeometry) geom); return convex_hull; } @@ -140,44 +167,52 @@ static boolean isConvex_(Geometry geom, ProgressTracker progress_tracker) { if (geom.isEmpty()) return true; // vacuously true - int type = geom.getType().value(); + Geometry.Type type = geom.getType(); - if (type == Geometry.GeometryType.Point) + if (type == Geometry.Type.Point) return true; // vacuously true - if (type == Geometry.GeometryType.Envelope) - return true; // always convex + if (type == Geometry.Type.Envelope) { + Envelope envelope = (Envelope) geom; + if (envelope.getXMin() == envelope.getXMax() || envelope.getYMin() == envelope.getYMax()) + return false; - if (MultiPath.isSegment(type)) - return false; // upgrade to polyline + return true; + } - if (type == Geometry.GeometryType.MultiPoint) { + if (MultiPath.isSegment(type.value())) { + Segment segment = (Segment) geom; + if (segment.getStartXY().equals(segment.getEndXY())) + return false; + + return true; // true, but we will upgrade to a Polyline for the ConvexHull operation + } + + if (type == Geometry.Type.MultiPoint) { MultiPoint multi_point = (MultiPoint) geom; if (multi_point.getPointCount() == 1) - return true; // vacuously true + return true; // vacuously true, but we will downgrade to a Point for the ConvexHull operation - return false; // upgrade to polyline if point count is 2, otherwise - // create convex hull + return false; } - if (type == Geometry.GeometryType.Polyline) { + if (type == Geometry.Type.Polyline) { Polyline polyline = (Polyline) geom; - if (polyline.getPathCount() == 1 && polyline.getPointCount() <= 2) - return true; // vacuously true + if (polyline.getPathCount() == 1 && polyline.getPointCount() == 2) { + if (!polyline.getXY(0).equals(polyline.getXY(1))) + return true; // vacuously true + } return false; // create convex hull } Polygon polygon = (Polygon) geom; - if (polygon.getPathCount() != 1) + if (polygon.getPathCount() != 1 || polygon.getPointCount() < 3) return false; - if (polygon.getPointCount() <= 2) - return true; // vacuously true - return ConvexHull.isPathConvex(polygon, 0, progress_tracker); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java index 9a1ac765..6f9d506c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -3,7 +3,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -28,21 +28,53 @@ *Export to GeoJson format. */ public abstract class OperatorExportToGeoJson extends Operator { - @Override - public Type getType() { - return Type.ExportToGeoJson; - } + @Override + public Type getType() { + return Type.ExportToGeoJson; + } - public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); + /** + * Performs the ExportToGeoJson operation + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometryCursor The cursor of geometries to write as GeoJson. + * @return Returns a JsonCursor. + */ + public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); - public abstract String execute(SpatialReference spatialReference, Geometry geometry); + /** + * Performs the ExportToGeoJson operation + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(SpatialReference spatialReference, Geometry geometry); - public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); - - public abstract String execute(Geometry geometry); + /** + * Performs the ExportToGeoJson operation + * @param exportFlags Use the {@link GeoJsonExportFlags} interface. + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); - public static OperatorExportToGeoJson local() { - return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ExportToGeoJson); - } + /** + * Performs the ExportToGeoJson operation. Will not write out a spatial reference or crs tag. Assumes the geometry is in wgs84. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(Geometry geometry); + + /** + * Performs the ExportToGeoJson operation on a spatial reference. + * + * @param export_flags The flags used for the export. + * @param spatial_reference The spatial reference being exported. Cannot be null. + * @return Returns the crs value object. + */ + public abstract String exportSpatialReference(int export_flags, SpatialReference spatial_reference); + + public static OperatorExportToGeoJson local() { + return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToGeoJson); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index ddad1662..0678cef4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -1,9 +1,32 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ /* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -23,312 +46,757 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; -import java.io.IOException; -import java.io.StringWriter; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; class OperatorExportToGeoJsonCursor extends JsonCursor { - GeometryCursor m_inputGeometryCursor; - int m_index; - int m_wkid = -1; - int m_latest_wkid = -1; - String m_wkt = null; - boolean m_preferMulti = false; - - private static JsonFactory factory = new JsonFactory(); - - public OperatorExportToGeoJsonCursor(boolean preferMulti, SpatialReference spatialReference, GeometryCursor geometryCursor) { - m_index = -1; - if (geometryCursor == null) - throw new IllegalArgumentException(); - if (spatialReference != null && !spatialReference.isLocal()) { - m_wkid = spatialReference.getOldID(); - m_wkt = spatialReference.getText(); - m_latest_wkid = spatialReference.getLatestID(); - } - m_inputGeometryCursor = geometryCursor; - m_preferMulti = preferMulti; - } - - public OperatorExportToGeoJsonCursor(GeometryCursor geometryCursor) { - m_index = -1; - - if (geometryCursor == null) - throw new IllegalArgumentException(); - - m_inputGeometryCursor = geometryCursor; - } - - @Override - public int getID() { - return m_index; - } - - @Override - public String next() { - Geometry geometry; - if ((geometry = m_inputGeometryCursor.next()) != null) { - m_index = m_inputGeometryCursor.getGeometryID(); - return exportToGeoJson(geometry); - } - return null; - } - - private String exportToGeoJson(Geometry geometry) { - StringWriter sw = new StringWriter(); - - try { - JsonGenerator g = factory.createJsonGenerator(sw); - - int type = geometry.getType().value(); - - switch (type) { - case Geometry.GeometryType.Point: - exportPointToGeoJson(g, (Point) geometry); - break; - case Geometry.GeometryType.MultiPoint: - exportMultiPointToGeoJson(g, (MultiPoint) geometry); - break; - case Geometry.GeometryType.Polyline: - exportMultiPathToGeoJson(g, (Polyline) geometry); - break; - case Geometry.GeometryType.Polygon: - exportMultiPathToGeoJson(g, (Polygon) geometry); - break; - case Geometry.GeometryType.Envelope: - exportEnvelopeToGeoJson(g, (Envelope) geometry); - break; - default: - throw new RuntimeException("not implemented for this geometry type"); - } - - return sw.getBuffer().toString(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - private void exportPointToGeoJson(JsonGenerator g, Point p) throws JsonGenerationException, IOException { - if (m_preferMulti) { - MultiPoint mp = new MultiPoint(); - mp.add(p); - exportMultiPointToGeoJson(g, mp); - return; - } - - g.writeStartObject(); - - g.writeFieldName("type"); - g.writeString("Point"); - - g.writeFieldName("coordinates"); - - if (p.isEmpty()) { - g.writeNull(); - } else { - g.writeStartArray(); - - writeDouble(p.getX(), g); - writeDouble(p.getY(), g); - - if (p.hasAttribute(Semantics.Z)) - writeDouble(p.getZ(), g); - - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws JsonGenerationException, IOException { - g.writeStartObject(); - - g.writeFieldName("type"); - g.writeString("MultiPoint"); - - g.writeFieldName("coordinates"); - - if (mp.isEmpty()) { - g.writeNull(); - } else { - g.writeStartArray(); - - MultiPointImpl mpImpl = (MultiPointImpl) mp._getImpl(); - AttributeStreamOfDbl zs = mp.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z) : null; - - Point2D p = new Point2D(); - - int n = mp.getPointCount(); - - for(int i = 0; i < n; i++) { - mp.getXY(i, p); - - g.writeStartArray(); - - writeDouble(p.x, g); - writeDouble(p.y, g); - - if (zs != null) - writeDouble(zs.get(i), g); - - g.writeEndArray(); - } - - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void exportMultiPathToGeoJson(JsonGenerator g, MultiPath p) throws JsonGenerationException, IOException { - MultiPathImpl pImpl = (MultiPathImpl) p._getImpl(); - - boolean isPolygon = pImpl.m_bPolygon; - int polyCount = isPolygon ? pImpl.getOGCPolygonCount() : 0; - - // check yo' polys playa - - g.writeStartObject(); - - g.writeFieldName("type"); - - boolean bCollection = false; - if (isPolygon) { - if (polyCount >= 2 || m_preferMulti) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 - g.writeString("MultiPolygon"); - bCollection = true; - } else { - g.writeString("Polygon"); - } - } - else { - if (p.getPathCount() > 1 || m_preferMulti) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 - g.writeString("MultiLineString"); - bCollection = true; - } else { - g.writeString("LineString"); - } - } - - g.writeFieldName("coordinates"); - - if (p.isEmpty()) { - g.writeNull(); - } else { - exportMultiPathToGeoJson(g, pImpl, bCollection); - } + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + int m_index; + int m_export_flags; + + public OperatorExportToGeoJsonCursor(int export_flags, SpatialReference spatialReference, + GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) + throw new IllegalArgumentException(); + + m_export_flags = export_flags; + m_spatialReference = spatialReference; + m_inputGeometryCursor = geometryCursor; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToGeoJson(m_export_flags, geometry, m_spatialReference); + } + return null; + } + + // Mirrors wkt + static String exportToGeoJson(int export_flags, Geometry geometry, SpatialReference spatial_reference) { + + if (geometry == null) + throw new IllegalArgumentException(""); + + JsonWriter json_writer = new JsonStringWriter(); + + json_writer.startObject(); + + exportGeometryToGeoJson_(export_flags, geometry, json_writer); + + if ((export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) == 0) { + json_writer.addFieldName("crs"); + exportSpatialReference(export_flags, spatial_reference, json_writer); + } + + json_writer.endObject(); + + return (String) json_writer.getJson(); + } + + static String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { + if (spatial_reference == null || (export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) != 0) + throw new IllegalArgumentException(""); + + JsonWriter json_writer = new JsonStringWriter(); + exportSpatialReference(export_flags, spatial_reference, json_writer); + + return (String) json_writer.getJson(); + } + + private static void exportGeometryToGeoJson_(int export_flags, Geometry geometry, JsonWriter json_writer) { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + exportPolygonToGeoJson_(export_flags, (Polygon) geometry, json_writer); + return; + + case Geometry.GeometryType.Polyline: + exportPolylineToGeoJson_(export_flags, (Polyline) geometry, json_writer); + return; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToGeoJson_(export_flags, (MultiPoint) geometry, json_writer); + return; + + case Geometry.GeometryType.Point: + exportPointToGeoJson_(export_flags, (Point) geometry, json_writer); + return; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToGeoJson_(export_flags, (Envelope) geometry, + json_writer); + return; + + default: + throw new RuntimeException("not implemented for this geometry type"); + } + } + + private static void exportSpatialReference(int export_flags, SpatialReference spatial_reference, + JsonWriter json_writer) { + if (spatial_reference != null) { + int wkid = spatial_reference.getLatestID(); + + if (wkid <= 0) + throw new GeometryException("invalid call"); + + json_writer.startObject(); + + json_writer.addFieldName("type"); + + json_writer.addValueString("name"); + + json_writer.addFieldName("properties"); + json_writer.startObject(); + + json_writer.addFieldName("name"); + + String authority = ((SpatialReferenceImpl) spatial_reference).getAuthority(); + authority = authority.toUpperCase(); + StringBuilder crs_identifier = new StringBuilder(authority); + crs_identifier.append(':'); + crs_identifier.append(wkid); + json_writer.addValueString(crs_identifier.toString()); + + json_writer.endObject(); + + json_writer.endObject(); + } else { + json_writer.addValueNull(); + } + } + + // Mirrors wkt + private static void exportPolygonToGeoJson_(int export_flags, Polygon polygon, JsonWriter json_writer) { + MultiPathImpl polygon_impl = (MultiPathImpl) (polygon._getImpl()); + + if ((export_flags & GeoJsonExportFlags.geoJsonExportFailIfNotSimple) != 0) { + int simple = polygon_impl.getIsSimple(0.0); + + if (simple != MultiPathImpl.GeometryXSimple.Strong) + throw new GeometryException("corrupted geometry"); + } + + int point_count = polygon.getPointCount(); + int polygon_count = polygon_impl.getOGCPolygonCount(); + + if (point_count > 0 && polygon_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = polygon_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = polygon_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + int path_count = 0; + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.POSITION); + path_flags = polygon_impl.getPathFlagsStreamRef(); + paths = polygon_impl.getPathStreamRef(); + path_count = polygon_impl.getPathCount(); + + if (b_export_zs) { + if (polygon_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (polygon_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.M); + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && polygon_count <= 1) + polygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, path_count, + json_writer); + else + multiPolygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, + paths, polygon_count, path_count, json_writer); + } + + // Mirrors wkt + private static void exportPolylineToGeoJson_(int export_flags, Polyline polyline, JsonWriter json_writer) { + MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); + + int point_count = polyline_impl.getPointCount(); + int path_count = polyline_impl.getPathCount(); + + if (point_count > 0 && path_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = polyline_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = polyline_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.POSITION); + path_flags = polyline_impl.getPathFlagsStreamRef(); + paths = polyline_impl.getPathStreamRef(); + + if (b_export_zs) { + if (polyline_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (polyline_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.M); + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && path_count <= 1) + lineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + json_writer); + else + multiLineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, + paths, path_count, json_writer); + } + + // Mirrors wkt + private static void exportMultiPointToGeoJson_(int export_flags, MultiPoint multipoint, JsonWriter json_writer) { + MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); + + int point_count = multipoint_impl.getPointCount(); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = multipoint_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = multipoint_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.POSITION); + + if (b_export_zs) { + if (multipoint_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (multipoint_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.M); + } + } + + multiPointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point_count, + json_writer); + } + + // Mirrors wkt + private static void exportPointToGeoJson_(int export_flags, Point point, JsonWriter json_writer) { + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + double x = NumberUtils.NaN(); + double y = NumberUtils.NaN(); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + + if (!point.isEmpty()) { + x = point.getX(); + y = point.getY(); + + if (b_export_zs) + z = point.getZ(); + + if (b_export_ms) + m = point.getM(); + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) + pointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + else + multiPointTaggedTextFromPoint_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void exportEnvelopeToGeoJson_(int export_flags, Envelope envelope, JsonWriter json_writer) { + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = envelope.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = envelope.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + double xmin = NumberUtils.NaN(); + double ymin = NumberUtils.NaN(); + double xmax = NumberUtils.NaN(); + double ymax = NumberUtils.NaN(); + double zmin = NumberUtils.NaN(); + double zmax = NumberUtils.NaN(); + double mmin = NumberUtils.NaN(); + double mmax = NumberUtils.NaN(); + + if (!envelope.isEmpty()) { + xmin = envelope.getXMin(); + ymin = envelope.getYMin(); + xmax = envelope.getXMax(); + ymax = envelope.getYMax(); + + Envelope1D interval; + + if (b_export_zs) { + interval = envelope.queryInterval(Semantics.Z, 0); + zmin = interval.vmin; + zmax = interval.vmax; + } + + if (b_export_ms) { + interval = envelope.queryInterval(Semantics.M, 0); + mmin = interval.vmin; + mmax = interval.vmax; + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) + polygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, + zmin, zmax, mmin, mmax, json_writer); + else + multiPolygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, + ymax, zmin, zmax, mmin, mmax, json_writer); + } + + // Mirrors wkt + private static void multiPolygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, + JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPolygon"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + multiPolygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + polygon_count, path_count, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiPolygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPolygon"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(xmin)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, + zmax, mmin, mmax, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiLineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiLineString"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + multiLineStringText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + path_count, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiPointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPoint"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + lineStringText_(false, false, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + point_count, json_writer); + } + + // Mirrors wkt + private static void multiPointTaggedTextFromPoint_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPoint"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(x)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void polygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Polygon"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, 0, path_count, + json_writer); + } + + // Mirrors wkt + private static void polygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Polygon"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(xmin)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, + zmax, mmin, mmax, json_writer); + } + + // Mirrors wkt + private static void lineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("LineString"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + paths.read(1), json_writer); + } + + // Mirrors wkt + private static void pointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Point"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(x)) { + json_writer.startArray(); + json_writer.endArray(); + + return; + } + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void multiPolygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, + JsonWriter json_writer) { + int polygon_start = 0; + int polygon_end = 1; + + while (polygon_end < path_count && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, + polygon_end, json_writer); + + for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { + polygon_start = polygon_end; + polygon_end++; + + while (polygon_end < path_count + && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, + polygon_end, json_writer); + } + } + + // Mirrors wkt + private static void multiLineStringText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + paths.read(1), json_writer); + + for (int path = 1; path < path_count; path++) { + b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); + + int istart = paths.read(path); + int iend = paths.read(path + 1); + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, + iend, json_writer); + } + } + + // Mirrors wkt + private static void polygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int polygon_start, int polygon_end, JsonWriter json_writer) { + json_writer.startArray(); + + int istart = paths.read(polygon_start); + int iend = paths.read(polygon_start + 1); + lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, iend, + json_writer); + + for (int path = polygon_start + 1; path < polygon_end; path++) { + istart = paths.read(path); + iend = paths.read(path + 1); + lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, + iend, json_writer); + } + + json_writer.endArray(); + } + + // Mirrors wkt + private static void lineStringText_(boolean bRing, boolean b_closed, int precision, boolean bFixedPoint, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, int istart, int iend, JsonWriter json_writer) { + if (istart == iend) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + if (bRing) { + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + + for (int point = iend - 1; point >= istart + 1; point--) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + } else { + for (int point = istart; point < iend - 1; point++) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, iend - 1, json_writer); + + if (b_closed) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + } + + json_writer.endArray(); + } + + // Mirrors wkt + private static int pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, JsonWriter json_writer) { + + json_writer.startArray(); + + json_writer.addValueDouble(x, precision, bFixedPoint); + json_writer.addValueDouble(y, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(z, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(m, precision, bFixedPoint); + + json_writer.endArray(); + + return 1; + } + + // Mirrors wkt + private static void pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, + JsonWriter json_writer) { + double x = position.readAsDbl(2 * point); + double y = position.readAsDbl(2 * point + 1); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + + if (b_export_zs) + z = (zs != null ? zs.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.Z)); + + if (b_export_ms) + m = (ms != null ? ms.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.M)); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void writeEnvelopeAsGeoJsonPolygon_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.startArray(); + json_writer.startArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmax, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmax, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmax, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmax, precision, bFixedPoint); + json_writer.addValueDouble(ymax, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymax, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmax, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmax, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); + + json_writer.endArray(); - g.writeEndObject(); - g.close(); - } - - private void exportMultiPathToGeoJson(JsonGenerator g, MultiPathImpl pImpl, boolean bCollection) throws IOException { - int startIndex; - int vertices; - - if (bCollection) - g.writeStartArray(); - - //AttributeStreamOfDbl position = (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); - //AttributeStreamOfInt8 pathFlags = pImpl.getPathFlagsStreamRef(); - //AttributeStreamOfInt32 paths = pImpl.getPathStreamRef(); - int pathCount = pImpl.getPathCount(); - boolean isPolygon = pImpl.m_bPolygon; - AttributeStreamOfDbl zs = pImpl.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(Semantics.Z) : null; - - for (int path = 0; path < pathCount; path++) { - startIndex = pImpl.getPathStart(path); - vertices = pImpl.getPathSize(path); - - boolean isExtRing = isPolygon && pImpl.isExteriorRing(path); - if (isExtRing) {//only for polygons - if (path > 0) - g.writeEndArray();//end of OGC polygon - - g.writeStartArray();//start of next OGC polygon - } - - writePath(pImpl, g, path, startIndex, vertices, zs); - } - - if (isPolygon) - g.writeEndArray();//end of last OGC polygon - - if (bCollection) - g.writeEndArray(); - } - - private void closePath(MultiPathImpl mp, JsonGenerator g, int startIndex, AttributeStreamOfDbl zs) throws IOException { - Point2D pt = new Point2D(); - - // close ring - mp.getXY(startIndex, pt); - g.writeStartArray(); - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (zs != null) - writeDouble(zs.get(startIndex), g); - - g.writeEndArray(); - } - - private void writePath(MultiPathImpl mp, JsonGenerator g, int pathIndex, int startIndex, int vertices, AttributeStreamOfDbl zs) throws IOException { - Point2D pt = new Point2D(); - - g.writeStartArray(); - - for (int i = startIndex; i < startIndex + vertices; i++) { - mp.getXY(i, pt); - g.writeStartArray(); - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (zs != null) - writeDouble(zs.get(i), g); - - g.writeEndArray(); - } - - if (mp.isClosedPath(pathIndex)) - closePath(mp, g, startIndex, zs); - - g.writeEndArray(); - } - - private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGenerationException, IOException { - boolean empty = e.isEmpty(); - - g.writeStartObject(); - g.writeFieldName("bbox"); - - if (empty) { - g.writeNull(); - } else { - g.writeStartArray(); - writeDouble(e.getXMin(), g); - writeDouble(e.getYMin(), g); - writeDouble(e.getXMax(), g); - writeDouble(e.getYMax(), g); - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void writeDouble(double d, JsonGenerator g) throws IOException, JsonGenerationException { - if (NumberUtils.isNaN(d)) { - g.writeNull(); - } else { - g.writeNumber(d); - } - - return; - } + json_writer.endArray(); + json_writer.endArray(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 0abbadb5..9da02767 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -3,7 +3,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -23,29 +23,28 @@ package com.esri.core.geometry; class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { - @Override - public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { - return new OperatorExportToGeoJsonCursor(false, spatialReference, geometryCursor); - } - - @Override - public String execute(SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(false, spatialReference, gc); - return cursor.next(); - } - - @Override - public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(exportFlags == GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, spatialReference, gc); - return cursor.next(); - } - - @Override - public String execute(Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(gc); - return cursor.next(); - } + @Override + public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { + return new OperatorExportToGeoJsonCursor(GeoJsonExportFlags.geoJsonExportDefaults, spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportDefaults, geometry, spatialReference); + } + + @Override + public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(exportFlags, geometry, spatialReference); + } + + @Override + public String execute(Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportSkipCRS, geometry, null); + } + + @Override + public String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { + return OperatorExportToGeoJsonCursor.exportSpatialReference(export_flags, spatial_reference); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java index b14fbb3e..9d24be43 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -24,442 +24,436 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.IOException; import java.util.Map; class OperatorExportToJsonCursor extends JsonCursor { - GeometryCursor m_inputGeometryCursor; - SpatialReference m_spatialReference; - int m_index; - - public OperatorExportToJsonCursor(SpatialReference spatialReference, - GeometryCursor geometryCursor) { - m_index = -1; - if (geometryCursor == null) { - throw new IllegalArgumentException(); - } - - m_inputGeometryCursor = geometryCursor; - m_spatialReference = spatialReference; - } - - @Override - public int getID() { - return m_index; - } - - @Override - public String next() { - Geometry geometry; - if ((geometry = m_inputGeometryCursor.next()) != null) { - m_index = m_inputGeometryCursor.getGeometryID(); - return exportToString(geometry, m_spatialReference, null); - } - return null; - } - - static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { - JsonWriter jsonWriter = new JsonStringWriter(); - exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); - return (String) jsonWriter.getJson(); - } - - private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - try { - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Point: - exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.MultiPoint: - exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Polyline: - exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Polygon: - exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Envelope: - exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); - break; - - default: - throw new RuntimeException( - "not implemented for this geometry type"); - } - - } catch (Exception e) { - e.printStackTrace(); - } - - } - - private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); - } - - private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); - } - - private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = pp.hasAttribute(Semantics.Z); - boolean bExportMs = pp.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (bExportZs) { - jsonWriter.addPairBoolean("hasZ", true); - } - - if (bExportMs) { - jsonWriter.addPairBoolean("hasM", true); - } - - jsonWriter.addPairArray(name); - - if (!pp.isEmpty()) { - int n = pp.getPathCount(); // rings or paths - - MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) { - zs = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.Z); - } - - if (bExportMs) { - ms = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.M); - } - - boolean bPolygon = pp instanceof Polygon; - Point2D pt = new Point2D(); - - for (int i = 0; i < n; i++) { - jsonWriter.addValueArray(); - int startindex = pp.getPathStart(i); - int numVertices = pp.getPathSize(i); - double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); - double z = NumberUtils.NaN(), m = NumberUtils.NaN(); - boolean bClosed = pp.isClosedPath(i); - for (int j = startindex; j < startindex + numVertices; j++) { - pp.getXY(j, pt); - - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDoubleF(pt.x, decimals); - jsonWriter.addValueDoubleF(pt.y, decimals); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - z = zs.get(j); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - m = ms.get(j); - jsonWriter.addValueDouble(m); - } - - if (j == startindex && bClosed) { - startx = pt.x; - starty = pt.y; - startz = z; - startm = m; - } - - jsonWriter.endArray(); - } - - // Close the Path/Ring by writing the Point at the start index - if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { - pp.getXY(startindex, pt); - // getPoint(startindex); - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDoubleF(pt.x, decimals); - jsonWriter.addValueDoubleF(pt.y, decimals); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - z = zs.get(startindex); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - m = ms.get(startindex); - jsonWriter.addValueDouble(m); - } - - jsonWriter.endArray(); - } - - jsonWriter.endArray(); - } - } - - jsonWriter.endArray(); - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = mpt.hasAttribute(Semantics.Z); - boolean bExportMs = mpt.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (bExportZs) { - jsonWriter.addPairBoolean("hasZ", true); - } - - if (bExportMs) { - jsonWriter.addPairBoolean("hasM", true); - } - - jsonWriter.addPairArray("points"); - - if (!mpt.isEmpty()) { - MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl - // for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) { - zs = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.Z); - } - - if (bExportMs) { - ms = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.M); - } - - Point2D pt = new Point2D(); - int n = mpt.getPointCount(); - for (int i = 0; i < n; i++) { - mpt.getXY(i, pt); - - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDoubleF(pt.x, decimals); - jsonWriter.addValueDoubleF(pt.y, decimals); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - double z = zs.get(i); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - double m = ms.get(i); - jsonWriter.addValueDouble(m); - } - - jsonWriter.endArray(); - } - } - - jsonWriter.endArray(); - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = pt.hasAttribute(Semantics.Z); - boolean bExportMs = pt.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (pt.isEmpty()) { - jsonWriter.addPairNull("x"); - jsonWriter.addPairNull("y"); - - if (bExportZs) { - jsonWriter.addPairNull("z"); - } - - if (bExportMs) { - jsonWriter.addPairNull("m"); - } - } else { - - if (bPositionAsF) { - jsonWriter.addPairDoubleF("x", pt.getX(), decimals); - jsonWriter.addPairDoubleF("y", pt.getY(), decimals); - } else { - jsonWriter.addPairDouble("x", pt.getX()); - jsonWriter.addPairDouble("y", pt.getY()); - } - - if (bExportZs) { - jsonWriter.addPairDouble("z", pt.getZ()); - } - - if (bExportMs) { - jsonWriter.addPairDouble("m", pt.getM()); - } - } - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = env.hasAttribute(Semantics.Z); - boolean bExportMs = env.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (env.isEmpty()) { - jsonWriter.addPairNull("xmin"); - jsonWriter.addPairNull("ymin"); - jsonWriter.addPairNull("xmax"); - jsonWriter.addPairNull("ymax"); - - if (bExportZs) { - jsonWriter.addPairNull("zmin"); - jsonWriter.addPairNull("zmax"); - } - - if (bExportMs) { - jsonWriter.addPairNull("mmin"); - jsonWriter.addPairNull("mmax"); - } - } else { - - if (bPositionAsF) { - jsonWriter.addPairDoubleF("xmin", env.getXMin(), decimals); - jsonWriter.addPairDoubleF("ymin", env.getYMin(), decimals); - jsonWriter.addPairDoubleF("xmax", env.getXMax(), decimals); - jsonWriter.addPairDoubleF("ymax", env.getYMax(), decimals); - } else { - jsonWriter.addPairDouble("xmin", env.getXMin()); - jsonWriter.addPairDouble("ymin", env.getYMin()); - jsonWriter.addPairDouble("xmax", env.getXMax()); - jsonWriter.addPairDouble("ymax", env.getYMax()); - } - - if (bExportZs) { - Envelope1D z = env.queryInterval(Semantics.Z, 0); - jsonWriter.addPairDouble("zmin", z.vmin); - jsonWriter.addPairDouble("zmax", z.vmax); - } - - if (bExportMs) { - Envelope1D m = env.queryInterval(Semantics.M, 0); - jsonWriter.addPairDouble("mmin", m.vmin); - jsonWriter.addPairDouble("mmax", m.vmax); - } - } - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { - int wkid = spatialReference.getOldID(); - if (wkid > 0) { - jsonWriter.addPairObject("spatialReference"); - - jsonWriter.addPairInt("wkid", wkid); - - int latest_wkid = spatialReference.getLatestID(); - if (latest_wkid > 0 && latest_wkid != wkid) { - jsonWriter.addPairInt("latestWkid", latest_wkid); - } - - jsonWriter.endObject(); - } else { - String wkt = spatialReference.getText(); - if (wkt != null) { - jsonWriter.addPairObject("spatialReference"); - jsonWriter.addPairString("wkt", wkt); - jsonWriter.endObject(); - } - } - } + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + int m_index; + + public OperatorExportToJsonCursor(SpatialReference spatialReference, GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) { + throw new IllegalArgumentException(); + } + + m_inputGeometryCursor = geometryCursor; + m_spatialReference = spatialReference; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToString(geometry, m_spatialReference, null); + } + return null; + } + + static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { + JsonWriter jsonWriter = new JsonStringWriter(); + exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); + return (String) jsonWriter.getJson(); + } + + private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + try { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Point: + exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polyline: + exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polygon: + exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); + break; + + default: + throw new RuntimeException("not implemented for this geometry type"); + } + + } catch (Exception e) { + } + + } + + private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pp.hasAttribute(Semantics.Z); + boolean bExportMs = pp.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray(name); + + if (!pp.isEmpty()) { + int n = pp.getPathCount(); // rings or paths + + MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); + } + + boolean bPolygon = pp instanceof Polygon; + Point2D pt = new Point2D(); + + for (int i = 0; i < n; i++) { + jsonWriter.addValueArray(); + int startindex = pp.getPathStart(i); + int numVertices = pp.getPathSize(i); + double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); + double z = NumberUtils.NaN(), m = NumberUtils.NaN(); + boolean bClosed = pp.isClosedPath(i); + for (int j = startindex; j < startindex + numVertices; j++) { + pp.getXY(j, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(j); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(j); + jsonWriter.addValueDouble(m); + } + + if (j == startindex && bClosed) { + startx = pt.x; + starty = pt.y; + startz = z; + startm = m; + } + + jsonWriter.endArray(); + } + + // Close the Path/Ring by writing the Point at the start index + if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { + pp.getXY(startindex, pt); + // getPoint(startindex); + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(startindex); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(startindex); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = mpt.hasAttribute(Semantics.Z); + boolean bExportMs = mpt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray("points"); + + if (!mpt.isEmpty()) { + MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl + // for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); + } + + Point2D pt = new Point2D(); + int n = mpt.getPointCount(); + for (int i = 0; i < n; i++) { + mpt.getXY(i, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + double z = zs.get(i); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + double m = ms.get(i); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pt.hasAttribute(Semantics.Z); + boolean bExportMs = pt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (pt.isEmpty()) { + jsonWriter.addPairNull("x"); + jsonWriter.addPairNull("y"); + + if (bExportZs) { + jsonWriter.addPairNull("z"); + } + + if (bExportMs) { + jsonWriter.addPairNull("m"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDouble("x", pt.getX(), decimals, true); + jsonWriter.addPairDouble("y", pt.getY(), decimals, true); + } else { + jsonWriter.addPairDouble("x", pt.getX()); + jsonWriter.addPairDouble("y", pt.getY()); + } + + if (bExportZs) { + jsonWriter.addPairDouble("z", pt.getZ()); + } + + if (bExportMs) { + jsonWriter.addPairDouble("m", pt.getM()); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = env.hasAttribute(Semantics.Z); + boolean bExportMs = env.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (env.isEmpty()) { + jsonWriter.addPairNull("xmin"); + jsonWriter.addPairNull("ymin"); + jsonWriter.addPairNull("xmax"); + jsonWriter.addPairNull("ymax"); + + if (bExportZs) { + jsonWriter.addPairNull("zmin"); + jsonWriter.addPairNull("zmax"); + } + + if (bExportMs) { + jsonWriter.addPairNull("mmin"); + jsonWriter.addPairNull("mmax"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDouble("xmin", env.getXMin(), decimals, true); + jsonWriter.addPairDouble("ymin", env.getYMin(), decimals, true); + jsonWriter.addPairDouble("xmax", env.getXMax(), decimals, true); + jsonWriter.addPairDouble("ymax", env.getYMax(), decimals, true); + } else { + jsonWriter.addPairDouble("xmin", env.getXMin()); + jsonWriter.addPairDouble("ymin", env.getYMin()); + jsonWriter.addPairDouble("xmax", env.getXMax()); + jsonWriter.addPairDouble("ymax", env.getYMax()); + } + + if (bExportZs) { + Envelope1D z = env.queryInterval(Semantics.Z, 0); + jsonWriter.addPairDouble("zmin", z.vmin); + jsonWriter.addPairDouble("zmax", z.vmax); + } + + if (bExportMs) { + Envelope1D m = env.queryInterval(Semantics.M, 0); + jsonWriter.addPairDouble("mmin", m.vmin); + jsonWriter.addPairDouble("mmax", m.vmax); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { + int wkid = spatialReference.getOldID(); + if (wkid > 0) { + jsonWriter.addPairObject("spatialReference"); + + jsonWriter.addPairInt("wkid", wkid); + + int latest_wkid = spatialReference.getLatestID(); + if (latest_wkid > 0 && latest_wkid != wkid) { + jsonWriter.addPairInt("latestWkid", latest_wkid); + } + + jsonWriter.endObject(); + } else { + String wkt = spatialReference.getText(); + if (wkt != null) { + jsonWriter.addPairObject("spatialReference"); + jsonWriter.addPairString("wkt", wkt); + jsonWriter.endObject(); + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index f07cea96..160bcffd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -25,9 +25,11 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; @@ -35,7 +37,9 @@ import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.HashMap; + import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; /** @@ -209,6 +213,20 @@ public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { return mapGeom; } + public static MapGeometry loadGeometryFromJSONStringDbg(String json) { + if (json == null) { + throw new IllegalArgumentException(); + } + + MapGeometry mapGeom = null; + try { + mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + } catch (Exception e) { + throw new IllegalArgumentException(e.toString()); + } + return mapGeom; + } + public static Geometry loadGeometryFromEsriShapeDbg(String file_name) { if (file_name == null) { throw new IllegalArgumentException(); diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java index 4e7068f7..03cbae60 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -33,7 +33,7 @@ public Operator.Type getType() { /** * Creates a geodesic buffer around the input geometries * - * @param input_geometries The geometries to buffer. + * @param inputGeometries The geometries to buffer. * @param sr The Spatial_reference of the Geometries. * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before * buffering. @@ -43,13 +43,15 @@ public Operator.Type getType() { * default deviation. * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. + * @param progressTracker Can be null. Allows to cancel lengthy operation. + * @return Geometry cursor over result buffers. */ abstract public GeometryCursor execute(GeometryCursor inputGeometries, SpatialReference sr, int curveType, double[] distancesMeters, double maxDeviationMeters, boolean bReserved, boolean bUnion, ProgressTracker progressTracker); /** * Creates a geodesic buffer around the input geometry * - * @param input_geometry The geometry to buffer. + * @param inputGeometry The geometry to buffer. * @param sr The Spatial_reference of the Geometry. * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before * buffering. @@ -57,6 +59,8 @@ public Operator.Type getType() { * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the * default deviation. * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. + * @param progressTracker Can be null. Allows to cancel lengthy operation. + * @return Returns result buffer. */ abstract public Geometry execute(Geometry inputGeometry, SpatialReference sr, int curveType, double distanceMeters, double maxDeviationMeters, boolean bReserved, ProgressTracker progressTracker); diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java index 5efcb134..ca2d1087 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -42,7 +42,7 @@ public Type getType() { * @param maxSegmentLengthMeters The maximum segment length (in meters) allowed. Must be a positive value. * @param sr The SpatialReference of the Geometry. * @param curveType The interpretation of a line connecting two points. - * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). * * Note the behavior is not determined for any geodetic curve segments that connect two poles, or for loxodrome segments that connect to any pole. */ diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 2c0c0287..88cb4653 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -24,8 +24,12 @@ package com.esri.core.geometry; import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; public abstract class OperatorImportFromGeoJson extends Operator { + @Override public Type getType() { return Type.ImportFromGeoJson; @@ -33,29 +37,40 @@ public Type getType() { /** * Performs the ImportFromGeoJson operation. + * + * @param type Use the {@link Geometry.Type} enum. + * @param jsonObject The JSONObject holding the geometry and spatial reference. + * @return Returns the imported MapGeometry. + * @throws JsonGeometryException + */ + public abstract MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, ProgressTracker progressTracker) throws JSONException; + + /** + * Deprecated, use version without import_flags. + * + * Performs the ImportFromGeoJson operation. + * * @param import_flags Use the {@link GeoJsonImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. + * @param type Use the {@link Geometry.Type} enum. * @param geoJsonString The string holding the Geometry in geoJson format. - * @return Returns the imported Geometry. - * @throws JSONException + * @return Returns the imported MapGeometry. + * @throws JSONException + * */ - public abstract MapGeometry execute(int import_flags, Geometry.Type type, - String geoJsonString, ProgressTracker progress_tracker) - throws JSONException; + public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; /** + * * Performs the ImportFromGeoJson operation. + * * @param import_flags Use the {@link GeoJsonImportFlags} interface. * @param geoJsonString The string holding the Geometry in geoJson format. * @return Returns the imported MapOGCStructure. - * @throws JSONException + * @throws JSONException */ - public abstract MapOGCStructure executeOGC(int import_flags, - String geoJsonString, ProgressTracker progress_tracker) - throws JSONException; + public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; public static OperatorImportFromGeoJson local() { - return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ImportFromGeoJson); + return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromGeoJson); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 8ed6b64e..e04952a4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -24,142 +24,1244 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; + +import java.io.IOException; import java.util.ArrayList; class OperatorImportFromGeoJsonLocal extends OperatorImportFromGeoJson { + @Override - public MapGeometry execute(int importFlags, Geometry.Type type, - String geoJsonString, ProgressTracker progress_tracker) - throws JSONException { - JSONObject geoJsonObject = new JSONObject(geoJsonString); - Geometry geometry = importGeometryFromGeoJson_(importFlags, type, - geoJsonObject); - SpatialReference spatialReference = importSpatialReferenceFromGeoJson_(geoJsonObject); - MapGeometry mapGeometry = new MapGeometry(geometry, spatialReference); - return mapGeometry; + public MapGeometry execute(int importFlags, Geometry.Type type, String geoJsonString, + ProgressTracker progressTracker) throws JSONException { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createJsonParser(geoJsonString); + + jsonParser.nextToken(); + + MapGeometry map_geometry = OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, + new JsonParserReader(jsonParser), progressTracker, false); + return map_geometry; + + } catch (JSONException jsonException) { + throw jsonException; + } catch (JsonParseException jsonParseException) { + throw new JSONException(jsonParseException.getMessage()); + } catch (IOException ioException) { + throw new JSONException(ioException.getMessage()); + } } - static JSONArray getJSONArray(JSONObject obj, String name) - throws JSONException { - if (obj.get(name) == JSONObject.NULL) - return new JSONArray(); - else - return obj.getJSONArray(name); + @Override + public MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, + ProgressTracker progressTracker) throws JSONException { + if (jsonObject == null) + return null; + + try { + return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, new JsonValueReader(jsonObject), + progressTracker, false); + } catch (JSONException jsonException) { + throw jsonException; + } catch (JsonParseException jsonParseException) { + throw new JSONException(jsonParseException.getMessage()); + } catch (IOException ioException) { + throw new JSONException(ioException.getMessage()); + } } - @Override - public MapOGCStructure executeOGC(int import_flags, String geoJsonString, - ProgressTracker progress_tracker) throws JSONException { - JSONObject geoJsonObject = new JSONObject(geoJsonString); - ArrayList structureStack = new ArrayList(0); - ArrayList objectStack = new ArrayList(0); - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); - - OGCStructure root = new OGCStructure(); - root.m_structures = new ArrayList(0); - structureStack.add(root); // add dummy root - objectStack.add(geoJsonObject); - indices.add(0); - numGeometries.add(1); - - while (!objectStack.isEmpty()) { - if (indices.getLast() == numGeometries.getLast()) { - structureStack.remove(structureStack.size() - 1); - indices.removeLast(); - numGeometries.removeLast(); - continue; - } - - OGCStructure lastStructure = structureStack.get(structureStack - .size() - 1); - JSONObject lastObject = objectStack.get(objectStack.size() - 1); - objectStack.remove(objectStack.size() - 1); - indices.write(indices.size() - 1, indices.getLast() + 1); - String typeString = lastObject.getString("type"); - - if (typeString.equalsIgnoreCase("GeometryCollection")) { - OGCStructure next = new OGCStructure(); - next.m_type = 7; - next.m_structures = new ArrayList(0); - lastStructure.m_structures.add(next); - structureStack.add(next); - - JSONArray geometries = getJSONArray(lastObject, "geometries"); - indices.add(0); - numGeometries.add(geometries.length()); - - for (int i = geometries.length() - 1; i >= 0; i--) - objectStack.add(geometries.getJSONObject(i)); + static final class OperatorImportFromGeoJsonHelper { + + private AttributeStreamOfDbl m_position; + private AttributeStreamOfDbl m_zs; + private AttributeStreamOfDbl m_ms; + private AttributeStreamOfInt32 m_paths; + private AttributeStreamOfInt8 m_path_flags; + private Point m_point; // special case for Points + private boolean m_b_has_zs; + private boolean m_b_has_ms; + private boolean m_b_has_zs_known; + private boolean m_b_has_ms_known; + private int m_num_embeddings; + + OperatorImportFromGeoJsonHelper() { + m_position = null; + m_zs = null; + m_ms = null; + m_paths = null; + m_path_flags = null; + m_point = null; + m_b_has_zs = false; + m_b_has_ms = false; + m_b_has_zs_known = false; + m_b_has_ms_known = false; + m_num_embeddings = 0; + } + + static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates) + throws JSONException, JsonParseException, IOException { + assert(json_iterator.currentToken() == JsonToken.START_OBJECT); + + OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + boolean b_type_found = false; + boolean b_coordinates_found = false; + boolean b_crs_found = false; + boolean b_crsURN_found = false; + String geo_json_type = null; + + Geometry geometry = null; + SpatialReference spatial_reference = null; + + JsonToken current_token; + String field_name = null; + + while ((current_token = json_iterator.nextToken()) != JsonToken.END_OBJECT) { + field_name = json_iterator.currentString(); + + if (field_name.equals("type")) { + if (b_type_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_type_found = true; + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + geo_json_type = json_iterator.currentString(); + } else if (field_name.equals("coordinates")) { + if (b_coordinates_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_coordinates_found = true; + current_token = json_iterator.nextToken(); + + if (skip_coordinates) { + json_iterator.skipChildren(); + } else {// According to the spec, the value of the + // coordinates must be an array. However, I do an + // extra check for null too. + if (current_token != JsonToken.VALUE_NULL) { + if (current_token != JsonToken.START_ARRAY) { + throw new IllegalArgumentException("invalid argument"); + } + + geo_json_helper.import_coordinates_(json_iterator, progress_tracker); + } + } + } else if (field_name.equals("crs")) { + if (b_crs_found || b_crsURN_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_crs_found = true; + current_token = json_iterator.nextToken(); + + if ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + spatial_reference = importSpatialReferenceFromCrs(json_iterator, progress_tracker); + else + json_iterator.skipChildren(); + } else if (field_name.equals("crsURN")) { + if (b_crs_found || b_crsURN_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_crsURN_found = true; + current_token = json_iterator.nextToken(); + + spatial_reference = importSpatialReferenceFromCrsUrn_(json_iterator, + progress_tracker); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + + // According to the spec, a GeoJSON object must have both a type and + // a coordinates array + if (!b_type_found || (!b_coordinates_found && !skip_coordinates)) { + throw new IllegalArgumentException("invalid argument"); + } + + if (!skip_coordinates) + geometry = geo_json_helper.createGeometry_(geo_json_type, type.value()); + + if (!b_crs_found && !b_crsURN_found && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + && ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0)) { + spatial_reference = SpatialReference.create(4326); // the spec + // gives a + // default + // of 4326 + // if no crs + // is given + } + + MapGeometry map_geometry = new MapGeometry(geometry, spatial_reference); + + assert(geo_json_helper.m_paths == null || (geo_json_helper.m_path_flags != null + && geo_json_helper.m_paths.size() == geo_json_helper.m_path_flags.size())); + + return map_geometry; + } + + // We have to import the coordinates in the most general way possible to + // not assume the type of geometry we're parsing. + // JSON allows for unordered objects, so it's possible that the + // coordinates array can come before the type tag when parsing + // sequentially, otherwise + // we would have to parse using a JSON_object, which would be easier, + // but not as space/time efficient. So this function blindly imports the + // coordinates + // into the attribute stream(s), and will later assign them to a + // geometry after the type tag is found. + private void import_coordinates_(JsonReader json_iterator, ProgressTracker progress_tracker) + throws JSONException, JsonParseException, IOException { + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + + int coordinates_level_lower = 1; + int coordinates_level_upper = 4; + + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 1) { + coordinates_level_upper = 1; + } + } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + if (coordinates_level_lower < 2) { + coordinates_level_lower = 2; + } + } else { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 1) {// special + // code + // for + // Points + readCoordinateAsPoint_(json_iterator); + } else { + boolean b_add_path_level_3 = true; + boolean b_polygon_start_level_4 = true; + + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 2) { + coordinates_level_upper = 2; + } + } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + if (coordinates_level_lower < 3) { + coordinates_level_lower = 3; + } + } else { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 2) {// LineString + // or + // MultiPoint + addCoordinate_(json_iterator); + } else { + boolean b_add_path_level_4 = true; + + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 3) { + coordinates_level_upper = 3; + } + } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + if (coordinates_level_lower < 4) { + coordinates_level_lower = 4; + } + } else { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 3) {// Polygon + // or + // MultiLineString + if (b_add_path_level_3) { + addPath_(); + b_add_path_level_3 = false; + } + + addCoordinate_(json_iterator); + } else { + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + json_iterator.nextToken(); + + if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (!isDouble_(json_iterator)) { + throw new IllegalArgumentException("invalid argument"); + } + + assert(coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 4); + // MultiPolygon + + if (b_add_path_level_4) { + addPath_(); + addPathFlag_(b_polygon_start_level_4); + b_add_path_level_4 = false; + b_polygon_start_level_4 = false; + } + + addCoordinate_(json_iterator); + } + + json_iterator.nextToken(); + } + } + + json_iterator.nextToken(); + } + } + + json_iterator.nextToken(); + } + } + + if (m_paths != null) { + m_paths.add(m_position.size() / 2); // add final path size + } + if (m_path_flags != null) { + m_path_flags.add((byte) 0); // to match the paths size + } + + m_num_embeddings = coordinates_level_lower; + } + + private void readCoordinateAsPoint_(JsonReader json_iterator) + throws JSONException, JsonParseException, IOException { + assert(isDouble_(json_iterator)); + + m_point = new Point(); + + double x = readDouble_(json_iterator); + json_iterator.nextToken(); + double y = readDouble_(json_iterator); + json_iterator.nextToken(); + + if (NumberUtils.isNaN(y)) { + x = NumberUtils.NaN(); + } + + m_point.setXY(x, y); + + if (isDouble_(json_iterator)) { + double z = readDouble_(json_iterator); + json_iterator.nextToken(); + m_point.setZ(z); + } + + if (isDouble_(json_iterator)) { + double m = readDouble_(json_iterator); + json_iterator.nextToken(); + m_point.setM(m); + } + + if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + throw new IllegalArgumentException("invalid argument"); + } + } + + private void addCoordinate_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { + assert(isDouble_(json_iterator)); + + if (m_position == null) { + m_position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + } + + double x = readDouble_(json_iterator); + json_iterator.nextToken(); + double y = readDouble_(json_iterator); + json_iterator.nextToken(); + + int size = m_position.size(); + + m_position.add(x); + m_position.add(y); + + if (isDouble_(json_iterator)) { + if (!m_b_has_zs_known) { + m_b_has_zs_known = true; + m_b_has_zs = true; + m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + } else { + if (!m_b_has_zs) { + m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, + VertexDescription.getDefaultValue(Semantics.Z)); + m_b_has_zs = true; + } + } + + double z = readDouble_(json_iterator); + json_iterator.nextToken(); + m_zs.add(z); + } else { + if (!m_b_has_zs_known) { + m_b_has_zs_known = true; + m_b_has_zs = false; + } else { + if (m_b_has_zs) { + m_zs.add(VertexDescription.getDefaultValue(Semantics.Z)); + } + } + } + + if (isDouble_(json_iterator)) { + if (!m_b_has_ms_known) { + m_b_has_ms_known = true; + m_b_has_ms = true; + m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + } else { + if (!m_b_has_ms) { + m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, + VertexDescription.getDefaultValue(Semantics.M)); + m_b_has_ms = true; + } + } + + double m = readDouble_(json_iterator); + json_iterator.nextToken(); + m_ms.add(m); + } else { + if (!m_b_has_ms_known) { + m_b_has_ms_known = true; + m_b_has_ms = false; + } else { + if (m_b_has_ms) { + m_zs.add(VertexDescription.getDefaultValue(Semantics.M)); + } + } + } + + if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + throw new IllegalArgumentException("invalid argument"); + } + } + + private void addPath_() { + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + } + + if (m_position == null) { + m_paths.add(0); + } else { + m_paths.add(m_position.size() / 2); + } + } + + private void addPathFlag_(boolean b_polygon_start) { + if (m_path_flags == null) { + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(0); + } + + if (b_polygon_start) { + m_path_flags.add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + } else { + m_path_flags.add((byte) PathFlags.enumClosed); + } + } + + private double readDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { + JsonToken current_token = json_iterator.currentToken(); + if (current_token == JsonToken.VALUE_NULL + || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + return NumberUtils.NaN(); + } else { + return json_iterator.currentDoubleValue(); + } + } + + private boolean isDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { + JsonToken current_token = json_iterator.currentToken(); + + if (current_token == JsonToken.VALUE_NUMBER_FLOAT) { + return true; + } + + if (current_token == JsonToken.VALUE_NUMBER_INT) { + return true; + } + + if (current_token == JsonToken.VALUE_NULL + || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + return true; + } + + return false; + } + + private Geometry createGeometry_(String geo_json_type, int type) + throws JSONException, JsonParseException, IOException { + Geometry geometry; + + if (type != Geometry.GeometryType.Unknown) { + switch (type) { + case Geometry.GeometryType.Polygon: + if (!geo_json_type.equals("MultiPolygon") && !geo_json_type.equals("Polygon")) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.Polyline: + if (!geo_json_type.equals("MultiLineString") && !geo_json_type.equals("LineString")) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.MultiPoint: + if (!geo_json_type.equals("MultiPoint")) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.Point: + if (!geo_json_type.equals("Point")) { + throw new GeometryException("invalid shape type"); + } + break; + default: + throw new GeometryException("invalid shape type"); + } + } + + if (m_position == null && m_point == null) { + if (geo_json_type.equals("Point")) { + if (m_num_embeddings > 1) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Point(); + } else if (geo_json_type.equals("MultiPoint")) { + if (m_num_embeddings > 2) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new MultiPoint(); + } else if (geo_json_type.equals("LineString")) { + if (m_num_embeddings > 2) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Polyline(); + } else if (geo_json_type.equals("MultiLineString")) { + if (m_num_embeddings > 3) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Polyline(); + } else if (geo_json_type.equals("Polygon")) { + if (m_num_embeddings > 3) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Polygon(); + } else if (geo_json_type.equals("MultiPolygon")) { + assert(m_num_embeddings <= 4); + geometry = new Polygon(); + } else { + throw new IllegalArgumentException("invalid argument"); + } + } else if (m_num_embeddings == 1) { + if (!geo_json_type.equals("Point")) { + throw new IllegalArgumentException("invalid argument"); + } + + assert(m_point != null); + geometry = m_point; + } else if (m_num_embeddings == 2) { + if (geo_json_type.equals("MultiPoint")) { + geometry = createMultiPointFromStreams_(); + } else if (geo_json_type.equals("LineString")) { + geometry = createPolylineFromStreams_(); + } else { + throw new IllegalArgumentException("invalid argument"); + } + } else if (m_num_embeddings == 3) { + if (geo_json_type.equals("Polygon")) { + geometry = createPolygonFromStreams_(); + } else if (geo_json_type.equals("MultiLineString")) { + geometry = createPolylineFromStreams_(); + } else { + throw new IllegalArgumentException("invalid argument"); + } } else { - int ogcType; + if (!geo_json_type.equals("MultiPolygon")) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = createPolygonFromStreams_(); + } + + return geometry; + } + + private Geometry createPolygonFromStreams_() { + assert(m_position != null); + assert(m_paths != null); + assert((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); + + Polygon polygon = new Polygon(); + MultiPathImpl multi_path_impl = (MultiPathImpl) polygon._getImpl(); + + checkPathPointCountsForMultiPath_(true); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + + if (m_b_has_zs) { + assert(m_zs != null); + multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert(m_ms != null); + multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + if (m_path_flags == null) { + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); + m_path_flags.setBits(0, (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + + for (int i = 1; i < m_path_flags.size() - 1; i++) { + m_path_flags.setBits(i, (byte) PathFlags.enumClosed); + } + } + + multi_path_impl.setPathStreamRef(m_paths); + multi_path_impl.setPathFlagsStreamRef(m_path_flags); + multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - if (typeString.equalsIgnoreCase("Point")) - ogcType = 1; - else if (typeString.equalsIgnoreCase("LineString")) - ogcType = 2; - else if (typeString.equalsIgnoreCase("Polygon")) - ogcType = 3; - else if (typeString.equalsIgnoreCase("MultiPoint")) - ogcType = 4; - else if (typeString.equalsIgnoreCase("MultiLineString")) - ogcType = 5; - else if (typeString.equalsIgnoreCase("MultiPolygon")) - ogcType = 6; - else - throw new UnsupportedOperationException(); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(m_path_flags); - Geometry geometry = importGeometryFromGeoJson_(import_flags, - Geometry.Type.Unknown, lastObject); + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + assert((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); + assert((m_path_flags.read(i) & PathFlags.enumClosed) != 0); + + if ((path_flags_clone.read(i) & PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) { + multi_path_impl.reversePath(i); // make clockwise + } + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(multi_path_impl, i)) { + multi_path_impl.reversePath(i); // make + // counter-clockwise + } + } + } + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); + multi_path_impl.setDirtyOGCFlags(false); + + return polygon; + } + + private Geometry createPolylineFromStreams_() { + assert(m_position != null); + assert((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); + assert(m_path_flags == null); + + Polyline polyline = new Polyline(); + MultiPathImpl multi_path_impl = (MultiPathImpl) polyline._getImpl(); + + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + m_paths.add(0); + m_paths.add(m_position.size() / 2); + } + + checkPathPointCountsForMultiPath_(false); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + + if (m_b_has_zs) { + assert(m_zs != null); + multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert(m_ms != null); + multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); + + multi_path_impl.setPathStreamRef(m_paths); + multi_path_impl.setPathFlagsStreamRef(m_path_flags); + multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + + return polyline; + } + + private Geometry createMultiPointFromStreams_() { + assert(m_position != null); + assert(m_paths == null); + assert(m_path_flags == null); - OGCStructure leaf = new OGCStructure(); - leaf.m_type = ogcType; - leaf.m_geometry = geometry; - lastStructure.m_structures.add(leaf); + MultiPoint multi_point = new MultiPoint(); + MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point._getImpl(); + multi_point_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + + if (m_b_has_zs) { + assert(m_zs != null); + multi_point_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert(m_ms != null); + multi_point_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + multi_point_impl.resize(m_position.size() / 2); + multi_point_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + + return multi_point; + } + + private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { + Point2D pt1 = new Point2D(), pt2 = new Point2D(); + double z1 = 0.0, z2 = 0.0, m1 = 0.0, m2 = 0.0; + int path_count = m_paths.size() - 1; + int guess_adjustment = 0; + + if (b_is_polygon) {// Polygon + guess_adjustment = path_count; // may remove up to path_count + // number of points + } else {// Polyline + for (int path = 0; path < path_count; path++) { + int path_size = m_paths.read(path + 1) - m_paths.read(path); + + if (path_size == 1) { + guess_adjustment--; // will add a point for each path + // containing only 1 point + } + } + + if (guess_adjustment == 0) { + return; // all paths are okay + } } + + AttributeStreamOfDbl adjusted_position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_position.size() - guess_adjustment); + AttributeStreamOfInt32 adjusted_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(m_paths.size()); + AttributeStreamOfDbl adjusted_zs = null; + AttributeStreamOfDbl adjusted_ms = null; + + if (m_b_has_zs) { + adjusted_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_zs.size() - guess_adjustment); + } + + if (m_b_has_ms) { + adjusted_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_ms.size() - guess_adjustment); + } + + int adjusted_start = 0; + adjusted_paths.write(0, 0); + + for (int path = 0; path < path_count; path++) { + int path_start = m_paths.read(path); + int path_end = m_paths.read(path + 1); + int path_size = path_end - path_start; + assert(path_size != 0); // we should not have added empty parts + // on import + + if (path_size == 1) { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, + path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start + 1, + path_start, path_size); + adjusted_start += 2; + } else if (path_size >= 3 && b_is_polygon) { + m_position.read(path_start * 2, pt1); + m_position.read((path_end - 1) * 2, pt2); + + if (m_b_has_zs) { + z1 = m_zs.readAsDbl(path_start); + z2 = m_zs.readAsDbl(path_end - 1); + } + + if (m_b_has_ms) { + m1 = m_ms.readAsDbl(path_start); + m2 = m_ms.readAsDbl(path_end - 1); + } + + if (pt1.equals(pt2) && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) + && (NumberUtils.isNaN(m1) && NumberUtils.isNaN(m2) || m1 == m2)) { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + path_start, path_size - 1); + adjusted_start += path_size - 1; + } else { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + path_start, path_size); + adjusted_start += path_size; + } + } else { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, + path_size); + adjusted_start += path_size; + } + adjusted_paths.write(path + 1, adjusted_start); + } + + m_position = adjusted_position; + m_paths = adjusted_paths; + m_zs = adjusted_zs; + m_ms = adjusted_ms; } - MapOGCStructure mapOGCStructure = new MapOGCStructure(); - mapOGCStructure.m_ogcStructure = root; - mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(geoJsonObject); + private void insertIntoAdjustedStreams_(AttributeStreamOfDbl adjusted_position, + AttributeStreamOfDbl adjusted_zs, AttributeStreamOfDbl adjusted_ms, int adjusted_start, int path_start, + int count) { + adjusted_position.insertRange(adjusted_start * 2, m_position, path_start * 2, count * 2, true, 2, + adjusted_start * 2); + + if (m_b_has_zs) { + adjusted_zs.insertRange(adjusted_start, m_zs, path_start, count, true, 1, adjusted_start); + } + + if (m_b_has_ms) { + adjusted_ms.insertRange(adjusted_start, m_ms, path_start, count, true, 1, adjusted_start); + } + } + + static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + // According to the spec, a null crs corresponds to no spatial + // reference + if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + return null; + } + + if (json_iterator.currentToken() == JsonToken.VALUE_STRING) {// see + // http://wiki.geojson.org/RFC-001 + // (this + // is + // deprecated, + // but + // there + // may + // be + // data + // with + // this + // format) + + String crs_short_form = json_iterator.currentString(); + int wkid = GeoJsonCrsTables.getWkidFromCrsShortForm(crs_short_form); + + if (wkid == -1) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = null; + + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + + return spatial_reference; + } + + if (json_iterator.currentToken() != JsonToken.START_OBJECT) { + throw new IllegalArgumentException("invalid argument"); + } + + // This is to support all cases of crs identifiers I've seen. Some + // may be rare or are legacy formats, but all are simple to + // accomodate. + boolean b_found_type = false; + boolean b_found_properties = false; + boolean b_found_properties_name = false; + boolean b_found_properties_href = false; + boolean b_found_properties_urn = false; + boolean b_found_properties_url = false; + boolean b_found_properties_code = false; + boolean b_found_esriwkt = false; + String crs_field = null, properties_field = null, type = null, crs_identifier_name = null, + crs_identifier_urn = null, crs_identifier_href = null, crs_identifier_url = null, esriwkt = null; + int crs_identifier_code = -1; + JsonToken current_token; + + while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + crs_field = json_iterator.currentString(); + + if (crs_field.equals("type")) { + if (b_found_type) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_type = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + type = json_iterator.currentString(); + } else if (crs_field.equals("properties")) { + if (b_found_properties) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.START_OBJECT) { + throw new IllegalArgumentException("invalid argument"); + } + + while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + properties_field = json_iterator.currentString(); + + if (properties_field.equals("name")) { + if (b_found_properties_name) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_name = true; + crs_identifier_name = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("href")) { + if (b_found_properties_href) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_href = true; + crs_identifier_href = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("urn")) { + if (b_found_properties_urn) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_urn = true; + crs_identifier_urn = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("url")) { + if (b_found_properties_url) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_url = true; + crs_identifier_url = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("code")) { + if (b_found_properties_code) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_code = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_NUMBER_INT) { + throw new IllegalArgumentException("invalid argument"); + } + + crs_identifier_code = json_iterator.currentIntValue(); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + } else if (crs_field.equals("esriwkt")) { + if (b_found_esriwkt) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_esriwkt = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + esriwkt = json_iterator.currentString(); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + + if ((!b_found_type || !b_found_properties) && !b_found_esriwkt) { + throw new IllegalArgumentException("invalid argument"); + } + + int wkid = -1; + + if (b_found_properties_name) { + wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_name); // see + // http://wiki.geojson.org/GeoJSON_draft_version_6 + // (most + // common) + } else if (b_found_properties_href) { + wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_href); // see + // http://wiki.geojson.org/GeoJSON_draft_version_6 + // (somewhat + // common) + } else if (b_found_properties_urn) { + wkid = GeoJsonCrsTables.getWkidFromCrsOgcUrn(crs_identifier_urn); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (b_found_properties_url) { + wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_url); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (b_found_properties_code) { + wkid = crs_identifier_code; // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (!b_found_esriwkt) { + throw new GeometryException("not implemented"); + } + + if (wkid < 0 && !b_found_esriwkt && !b_found_properties_name) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = null; + + if (wkid > 0) { + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + } + + if (spatial_reference == null) { + try { + if (b_found_esriwkt) {// I exported crs wkt strings like + // this + spatial_reference = SpatialReference.create(esriwkt); + } else if (b_found_properties_name) {// AGOL exported crs + // wkt strings like + // this where the + // crs identifier of + // the properties + // name is like + // "ESRI:" + String potential_wkt = GeoJsonCrsTables.getWktFromCrsName(crs_identifier_name); + spatial_reference = SpatialReference.create(potential_wkt); + } + } catch (Exception e) { + } + } + + return spatial_reference; + } + + // see http://geojsonwg.github.io/draft-geojson/draft.html + static SpatialReference importSpatialReferenceFromCrsUrn_(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + // According to the spec, a null crs corresponds to no spatial + // reference + if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + return null; + } + + if (json_iterator.currentToken() != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + String crs_identifier_urn = json_iterator.currentString(); + + int wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_urn); // This + // will + // check + // for + // short + // form + // name, + // as + // well + // as + // long + // form + // URNs + + if (wkid == -1) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = SpatialReference.create(wkid); + + return spatial_reference; + } + + private static String getCrsIdentifier_(JsonReader json_iterator) + throws JSONException, JsonParseException, IOException { + JsonToken current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + return json_iterator.currentString(); + } + + } + + static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { + if (obj.get(name) == JSONObject.NULL) + return new JSONArray(); + else + return obj.getJSONArray(name); + } + + @Override + public MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) + throws JSONException { + MapOGCStructure mapOGCStructure = null; + try { + JSONObject geoJsonObject = new JSONObject(geoJsonString); + ArrayList structureStack = new ArrayList(0); + ArrayList objectStack = new ArrayList(0); + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + structureStack.add(root); // add dummy root + objectStack.add(geoJsonObject); + indices.add(0); + numGeometries.add(1); + while (!objectStack.isEmpty()) { + if (indices.getLast() == numGeometries.getLast()) { + structureStack.remove(structureStack.size() - 1); + indices.removeLast(); + numGeometries.removeLast(); + continue; + } + OGCStructure lastStructure = structureStack.get(structureStack.size() - 1); + JSONObject lastObject = objectStack.get(objectStack.size() - 1); + objectStack.remove(objectStack.size() - 1); + indices.write(indices.size() - 1, indices.getLast() + 1); + String typeString = lastObject.getString("type"); + if (typeString.equalsIgnoreCase("GeometryCollection")) { + OGCStructure next = new OGCStructure(); + next.m_type = 7; + next.m_structures = new ArrayList(0); + lastStructure.m_structures.add(next); + structureStack.add(next); + JSONArray geometries = getJSONArray(lastObject, "geometries"); + indices.add(0); + numGeometries.add(geometries.length()); + for (int i = geometries.length() - 1; i >= 0; i--) + objectStack.add(geometries.getJSONObject(i)); + } else { + int ogcType; + if (typeString.equalsIgnoreCase("Point")) + ogcType = 1; + else if (typeString.equalsIgnoreCase("LineString")) + ogcType = 2; + else if (typeString.equalsIgnoreCase("Polygon")) + ogcType = 3; + else if (typeString.equalsIgnoreCase("MultiPoint")) + ogcType = 4; + else if (typeString.equalsIgnoreCase("MultiLineString")) + ogcType = 5; + else if (typeString.equalsIgnoreCase("MultiPolygon")) + ogcType = 6; + else + throw new UnsupportedOperationException(); + + MapGeometry map_geometry = execute(import_flags | GeoJsonImportFlags.geoJsonImportSkipCRS, + Geometry.Type.Unknown, lastObject, null); + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogcType; + leaf.m_geometry = map_geometry.getGeometry(); + lastStructure.m_structures.add(leaf); + } + } + mapOGCStructure = new MapOGCStructure(); + mapOGCStructure.m_ogcStructure = root; + + if ((import_flags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(import_flags, geoJsonObject); + } catch (JSONException jsonException) { + throw jsonException; + } catch (JsonParseException jsonParseException) { + throw new JSONException(jsonParseException.getMessage()); + } catch (IOException ioException) { + throw new JSONException(ioException.getMessage()); + } return mapOGCStructure; } - private static SpatialReference importSpatialReferenceFromGeoJson_( - JSONObject crsJSONObject) throws JSONException { - String wkidString = crsJSONObject.optString("crs", ""); + private static SpatialReference importSpatialReferenceFromGeoJson_(int importFlags, JSONObject crsJSONObject) + throws JSONException, JsonParseException, IOException { + + SpatialReference spatial_reference = null; + boolean b_crs_found = false, b_crsURN_found = false; - if (wkidString.equals("")) { - return SpatialReference.create(4326); + Object opt = crsJSONObject.opt("crs"); + + if (opt != null) { + b_crs_found = true; + JSONObject crs_object = new JSONObject(); + crs_object.put("crs", opt); + JsonValueReader json_iterator = new JsonValueReader(crs_object); + json_iterator.nextToken(); + json_iterator.nextToken(); + return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); } - // wkidString will be of the form "EPSG:#" where # is an integer, the - // EPSG ID. - // If the ID is below 32,767, then the EPSG ID will agree with the - // well-known (WKID). + opt = crsJSONObject.opt("crsURN"); - if (wkidString.length() <= 5) { - throw new IllegalArgumentException(); + if (opt != null) { + b_crsURN_found = true; + JSONObject crs_object = new JSONObject(); + crs_object.put("crsURN", opt); + JsonValueReader json_iterator = new JsonValueReader(crs_object); + json_iterator.nextToken(); + json_iterator.nextToken(); + return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); } - // Throws a JSON exception if this cannot appropriately be converted to - // an integer. - int wkid = Integer.valueOf(wkidString.substring(5)).intValue(); + if ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0) { + spatial_reference = SpatialReference.create(4326); + } - return SpatialReference.create(wkid); + return spatial_reference; } - private static Geometry importGeometryFromGeoJson_(int importFlags, - Geometry.Type type, JSONObject geometryJSONObject) - throws JSONException { + /* + private static Geometry importGeometryFromGeoJson_(int importFlags, Geometry.Type type, + JSONObject geometryJSONObject) throws JSONException { String typeString = geometryJSONObject.getString("type"); - JSONArray coordinateArray = getJSONArray(geometryJSONObject, - "coordinates"); - + JSONArray coordinateArray = getJSONArray(geometryJSONObject, "coordinates"); if (typeString.equalsIgnoreCase("MultiPolygon")) { if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) throw new IllegalArgumentException("invalid shapetype"); @@ -169,8 +1271,7 @@ private static Geometry importGeometryFromGeoJson_(int importFlags, throw new IllegalArgumentException("invalid shapetype"); return lineStringTaggedText_(true, importFlags, coordinateArray); } else if (typeString.equalsIgnoreCase("MultiPoint")) { - if (type != Geometry.Type.MultiPoint - && type != Geometry.Type.Unknown) + if (type != Geometry.Type.MultiPoint && type != Geometry.Type.Unknown) throw new IllegalArgumentException("invalid shapetype"); return multiPointTaggedText_(importFlags, coordinateArray); } else if (typeString.equalsIgnoreCase("Polygon")) { @@ -190,8 +1291,8 @@ private static Geometry importGeometryFromGeoJson_(int importFlags, } } - private static Geometry polygonTaggedText_(boolean bMultiPolygon, - int importFlags, JSONArray coordinateArray) throws JSONException { + private static Geometry polygonTaggedText_(boolean bMultiPolygon, int importFlags, JSONArray coordinateArray) + throws JSONException { MultiPath multiPath; MultiPathImpl multiPathImpl; AttributeStreamOfDbl zs = null; @@ -199,49 +1300,30 @@ private static Geometry polygonTaggedText_(boolean bMultiPolygon, AttributeStreamOfDbl position; AttributeStreamOfInt32 paths; AttributeStreamOfInt8 path_flags; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( - 1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - + position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); multiPath = new Polygon(); multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiPolygon) { - pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, - coordinateArray); + pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, coordinateArray); } else { - pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, - coordinateArray); + pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, coordinateArray); } - if (pointCount != 0) { - assert (2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); + assert(2 * pointCount == position.size()); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); multiPathImpl.setPathStreamRef(paths); multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); } - if (ms != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( - path_flags); - + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(path_flags); for (int i = 0; i < path_flags_clone.size() - 1; i++) { if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should // be @@ -253,23 +1335,17 @@ private static Geometry polygonTaggedText_(boolean bMultiPolygon, multiPathImpl.reversePath(i); // make counter-clockwise } } - multiPathImpl.setPathFlagsStreamRef(path_flags_clone); - } - if ((importFlags & (int) GeoJsonImportFlags.geoJsonImportNonTrusted) == 0) { - multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, - false); + multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, false); } - multiPathImpl.setDirtyOGCFlags(false); - return multiPath; } - private static Geometry lineStringTaggedText_(boolean bMultiLineString, - int importFlags, JSONArray coordinateArray) throws JSONException { + private static Geometry lineStringTaggedText_(boolean bMultiLineString, int importFlags, JSONArray coordinateArray) + throws JSONException { MultiPath multiPath; MultiPathImpl multiPathImpl; AttributeStreamOfDbl zs = null; @@ -277,265 +1353,190 @@ private static Geometry lineStringTaggedText_(boolean bMultiLineString, AttributeStreamOfDbl position; AttributeStreamOfInt32 paths; AttributeStreamOfInt8 path_flags; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( - 1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - + position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); multiPath = new Polyline(); multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiLineString) { - pointCount = multiLineStringText_(zs, ms, position, paths, - path_flags, coordinateArray); + pointCount = multiLineStringText_(zs, ms, position, paths, path_flags, coordinateArray); } else { - pointCount = lineStringText_(false, zs, ms, position, paths, - path_flags, coordinateArray); + pointCount = lineStringText_(false, zs, ms, position, paths, path_flags, coordinateArray); } - if (pointCount != 0) { - assert (2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); + assert(2 * pointCount == position.size()); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); multiPathImpl.setPathStreamRef(paths); multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); } - if (ms != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); } - return multiPath; } - private static Geometry multiPointTaggedText_(int importFlags, - JSONArray coordinateArray) throws JSONException { + private static Geometry multiPointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { MultiPoint multiPoint; MultiPointImpl multiPointImpl; AttributeStreamOfDbl zs = null; AttributeStreamOfDbl ms = null; AttributeStreamOfDbl position; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - + position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); multiPoint = new MultiPoint(); multiPointImpl = (MultiPointImpl) multiPoint._getImpl(); - int pointCount = multiPointText_(zs, ms, position, coordinateArray); - if (pointCount != 0) { - assert (2 * pointCount == position.size()); + assert(2 * pointCount == position.size()); multiPointImpl.resize(pointCount); - multiPointImpl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - + multiPointImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); multiPointImpl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); } - return multiPoint; } - private static Geometry pointTaggedText_(int importFlags, - JSONArray coordinateArray) throws JSONException { + private static Geometry pointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { Point point = new Point(); - int length = coordinateArray.length(); - if (length == 0) { point.setEmpty(); return point; } - - point.setXY(getDouble_(coordinateArray, 0), - getDouble_(coordinateArray, 1)); - + point.setXY(getDouble_(coordinateArray, 0), getDouble_(coordinateArray, 1)); return point; } - private static int multiPolygonText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + private static int multiPolygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, JSONArray coordinateArray) throws JSONException { // At start of MultiPolygonText - int totalPointCount = 0; int length = coordinateArray.length(); - if (length == 0) return totalPointCount; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a polygon, but it is - // not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // polygon, but it is not a JSONArray. throw new IllegalArgumentException(""); } // At start of PolygonText - - totalPointCount = polygonText_(zs, ms, position, paths, path_flags, - totalPointCount, subArray); + totalPointCount = polygonText_(zs, ms, position, paths, path_flags, totalPointCount, subArray); } - return totalPointCount; } - private static int multiLineStringText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + private static int multiLineStringText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, JSONArray coordinateArray) throws JSONException { // At start of MultiLineStringText - int totalPointCount = 0; int length = coordinateArray.length(); - if (length == 0) return totalPointCount; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a line string, but - // it is not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // line string, but it is not a JSONArray. throw new IllegalArgumentException(""); } // At start of LineStringText - - totalPointCount += lineStringText_(false, zs, ms, position, paths, - path_flags, subArray); + totalPointCount += lineStringText_(false, zs, ms, position, paths, path_flags, subArray); } - return totalPointCount; } - - private static int multiPointText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + + private static int multiPointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, JSONArray coordinateArray) throws JSONException { // At start of MultiPointText - int pointCount = 0; - for (int current = 0; current < coordinateArray.length(); current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a point, but it is - // not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // point, but it is not a JSONArray. throw new IllegalArgumentException(""); } - pointCount += pointText_(zs, ms, position, subArray); } - return pointCount; } - private static int polygonText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - int totalPointCount, JSONArray coordinateArray) - throws JSONException { + private static int polygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, int totalPointCount, + JSONArray coordinateArray) throws JSONException { // At start of PolygonText - int length = coordinateArray.length(); - if (length == 0) { return totalPointCount; } - boolean bFirstLineString = true; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a line string, but - // it is not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // line string, but it is not a JSONArray. throw new IllegalArgumentException(""); } - // At start of LineStringText - - int pointCount = lineStringText_(true, zs, ms, position, paths, - path_flags, subArray); - + int pointCount = lineStringText_(true, zs, ms, position, paths, path_flags, subArray); if (pointCount != 0) { if (bFirstLineString) { bFirstLineString = false; - path_flags.setBits(path_flags.size() - 2, - (byte) PathFlags.enumOGCStartPolygon); + path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumOGCStartPolygon); } - - path_flags.setBits(path_flags.size() - 2, - (byte) PathFlags.enumClosed); + path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumClosed); totalPointCount += pointCount; } } - return totalPointCount; } - - private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + + private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, JSONArray coordinateArray) throws JSONException { // At start of LineStringText - int pointCount = 0; int length = coordinateArray.length(); - if (length == 0) return pointCount; - boolean bStartPath = true; double startX = NumberUtils.TheNaN; double startY = NumberUtils.TheNaN; double startZ = NumberUtils.TheNaN; double startM = NumberUtils.TheNaN; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a single point, but - // it is not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // single point, but it is not a JSONArray. throw new IllegalArgumentException(""); } - // At start of x - double x = getDouble_(subArray, 0); double y = getDouble_(subArray, 1); double z = NumberUtils.TheNaN; double m = NumberUtils.TheNaN; - boolean bAddPoint = true; - - if (bRing && pointCount >= 2 && current == length - 1) { - // If the last point in the ring is not equal to the start - // point, then let's add it. - - if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils - .isNaN(x))) - && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils - .isNaN(y)))) { + if (bRing && pointCount >= 2 && current == length - 1) {// If the + // last + // point in + // the ring + // is not + // equal to + // the start + // point, + // then + // let's add + // it. + if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils.isNaN(x))) + && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils.isNaN(y)))) { bAddPoint = false; } } - if (bAddPoint) { if (bStartPath) { bStartPath = false; @@ -544,72 +1545,54 @@ private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, startZ = z; startM = m; } - pointCount++; addToStreams_(zs, ms, position, x, y, z, m); } } - if (pointCount == 1) { pointCount++; addToStreams_(zs, ms, position, startX, startY, startZ, startM); } - paths.add(position.size() / 2); path_flags.add((byte) 0); - return pointCount; } - private static int pointText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + private static int pointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, JSONArray coordinateArray) throws JSONException { // At start of PointText - int length = coordinateArray.length(); - if (length == 0) return 0; - // At start of x - double x = getDouble_(coordinateArray, 0); double y = getDouble_(coordinateArray, 1); double z = NumberUtils.TheNaN; double m = NumberUtils.TheNaN; - addToStreams_(zs, ms, position, x, y, z, m); - return 1; } - private static void addToStreams_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, double x, - double y, double z, double m) { + private static void addToStreams_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + double x, double y, double z, double m) { position.add(x); position.add(y); - if (zs != null) zs.add(z); - if (ms != null) ms.add(m); } - private static double getDouble_(JSONArray coordinateArray, int index) - throws JSONException { + private static double getDouble_(JSONArray coordinateArray, int index) throws JSONException { if (index < 0 || index >= coordinateArray.length()) { throw new IllegalArgumentException(""); } - if (coordinateArray.isNull(index)) { return NumberUtils.TheNaN; } - if (coordinateArray.optDouble(index, NumberUtils.TheNaN) != NumberUtils.TheNaN) { return coordinateArray.getDouble(index); } - throw new IllegalArgumentException(""); - } + }*/ } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index 56f79a87..8c267d7a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -271,7 +271,6 @@ static MapGeometry importFromJsonParser(int gt, JsonReader parser) { mp = new MapGeometry(geometry, spatial_reference); } catch (Exception e) { - e.printStackTrace(); return null; } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index 2f23f11c..ee3b4833 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -59,8 +59,8 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate *what dimensions of geometry one wants to be returned. For example, to return *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. - *@return Returns the cursor of the intersection result. The cursors' get_geometry_ID method returns the current ID of the input geometry - *being processed. Wh dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number + *@return Returns the cursor of the intersection result. The cursors' getGeometryID method returns the current ID of the input geometry + *being processed. When dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. * *The operator intersects every geometry in the input_geometries with the first geometry of the intersector and returns the result. @@ -68,7 +68,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *Note, when the dimensionMask is -1, then for each intersected pair of geometries, *the result has the lower of dimentions of the two geometries. That is, the dimension of the Polyline/Polyline intersection *is always 1 (that is, for polylines it never returns crossing points, but the overlaps only). - *If dimensionMask is 7, the operation will return any possible + *If dimensionMask is 7, the operation will return any possible intersections. */ public abstract GeometryCursor execute(GeometryCursor input_geometries, GeometryCursor intersector, SpatialReference sr, diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java index 3a4a31ce..b25d66e0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -43,7 +43,7 @@ public Type getType() { * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. - * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). * * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. */ @@ -58,7 +58,7 @@ public Type getType() { * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. - * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). * * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. */ diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 0f553257..2343c5df 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -33,10 +33,9 @@ * location in a two-dimensional XY-Plane. In case of Geographic Coordinate * Systems, the X coordinate is the longitude and the Y is the latitude. */ -public final class Point extends Geometry implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use - // writeReplace and - // GeometrySerializer +public class Point extends Geometry implements Serializable { + //We are using writeReplace instead. + //private static final long serialVersionUID = 2L; double[] m_attributes; // use doubles to store everything (long are bitcast) @@ -47,7 +46,7 @@ public Point() { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); } - Point(VertexDescription vd) { + public Point(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; @@ -129,7 +128,7 @@ public final void setXY(Point2D pt) { /** * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. */ - Point3D getXYZ() { + public Point3D getXYZ() { if (isEmptyImpl()) throw new GeometryException( "This operation should not be performed on an empty geometry."); @@ -151,7 +150,7 @@ Point3D getXYZ() { * @param pt * The point to create the XYZ coordinate from. */ - void setXYZ(Point3D pt) { + public void setXYZ(Point3D pt) { _touch(); boolean bHasZ = hasAttribute(Semantics.Z); if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add @@ -388,7 +387,7 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[newDescription._getTotalComponents()]; + double[] newAttributes = new double[newDescription.getTotalComponentCount()]; int j = 0; for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { @@ -424,9 +423,9 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { * Sets the Point to a default, non-empty state. */ void _setToDefault() { - resizeAttributes(m_description._getTotalComponents()); + resizeAttributes(m_description.getTotalComponentCount()); Point.attributeCopy(m_description._getDefaultPointAttributes(), - m_attributes, m_description._getTotalComponents()); + m_attributes, m_description.getTotalComponentCount()); m_attributes[0] = NumberUtils.NaN(); m_attributes[1] = NumberUtils.NaN(); } @@ -465,9 +464,9 @@ public void copyTo(Geometry dst) { pointDst.assignVertexDescription(m_description); } else { pointDst.assignVertexDescription(m_description); - pointDst.resizeAttributes(m_description._getTotalComponents()); + pointDst.resizeAttributes(m_description.getTotalComponentCount()); attributeCopy(m_attributes, pointDst.m_attributes, - m_description._getTotalComponents()); + m_description.getTotalComponentCount()); } } @@ -595,7 +594,7 @@ public boolean equals(Object _other) { else return false; - for (int i = 0, n = m_description._getTotalComponents(); i < n; i++) + for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) if (m_attributes[i] != otherPt.m_attributes[i]) return false; @@ -610,7 +609,7 @@ public boolean equals(Object _other) { public int hashCode() { int hashCode = m_description.hashCode(); if (!isEmptyImpl()) { - for (int i = 0, n = m_description._getTotalComponents(); i < n; i++) { + for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) { long bits = Double.doubleToLongBits(m_attributes[i]); int hc = (int) (bits ^ (bits >>> 32)); hashCode = NumberUtils.hash(hashCode, hc); diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index f9fa71ab..245b8156 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -48,6 +48,10 @@ public Point2D(double x, double y) { this.y = y; } + public Point2D(Point2D other) { + setCoords(other); + } + public static Point2D construct(double x, double y) { return new Point2D(x, y); } @@ -122,20 +126,29 @@ public void negate(Point2D other) { } public void interpolate(Point2D other, double alpha) { - x = x * (1.0 - alpha) + other.x * alpha; - y = y * (1.0 - alpha) + other.y * alpha; + MathUtils.lerp(this, other, alpha, this); } public void interpolate(Point2D p1, Point2D p2, double alpha) { - x = p1.x * (1.0 - alpha) + p2.x * alpha; - y = p1.y * (1.0 - alpha) + p2.y * alpha; + MathUtils.lerp(p1, p2, alpha, this); } - + + /** + * Calculates this = this * f + shift + * @param f + * @param shift + */ public void scaleAdd(double f, Point2D shift) { x = x * f + shift.x; y = y * f + shift.y; } + /** + * Calculates this = other * f + shift + * @param f + * @param other + * @param shift + */ public void scaleAdd(double f, Point2D other, Point2D shift) { x = other.x * f + shift.x; y = other.y * f + shift.y; @@ -152,12 +165,19 @@ public void scale(double f) { } /** - * Compares two vertices lexicographicaly. + * Compares two vertices lexicographically by y. */ public int compare(Point2D other) { return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 : (x > other.x ? 1 : 0))); } + /** + * Compares two vertices lexicographically by x. + */ + int compareX(Point2D other) { + return x < other.x ? -1 : (x > other.x ? 1 : (y < other.y ? -1 + : (y > other.y ? 1 : 0))); + } public void normalize(Point2D other) { double len = other.length(); @@ -266,7 +286,7 @@ void _setNan() { } boolean _isNan() { - return NumberUtils.isNaN(x); + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); } // calculates which quarter of xy plane the vector lies in. First quater is @@ -475,9 +495,269 @@ public static int orientationRobust(Point2D p, Point2D q, Point2D r) { return det_mp.signum(); } + private static int inCircleRobustMP_(Point2D p, Point2D q, Point2D r, Point2D s) { + BigDecimal sx_mp = new BigDecimal(s.x), sy_mp = new BigDecimal(s.y); + + BigDecimal psx_mp = new BigDecimal(p.x), psy_mp = new BigDecimal(p.y); + psx_mp = psx_mp.subtract(sx_mp); + psy_mp = psy_mp.subtract(sy_mp); + + BigDecimal qsx_mp = new BigDecimal(q.x), qsy_mp = new BigDecimal(q.y); + qsx_mp = qsx_mp.subtract(sx_mp); + qsy_mp = qsy_mp.subtract(sy_mp); + + BigDecimal rsx_mp = new BigDecimal(r.x), rsy_mp = new BigDecimal(r.y); + rsx_mp = rsx_mp.subtract(sx_mp); + rsy_mp = rsy_mp.subtract(sy_mp); + + BigDecimal pq_det_mp = psx_mp.multiply(qsy_mp).subtract(psy_mp.multiply(qsx_mp)); + BigDecimal qr_det_mp = qsx_mp.multiply(rsy_mp).subtract(qsy_mp.multiply(rsx_mp)); + BigDecimal pr_det_mp = psx_mp.multiply(rsy_mp).subtract(psy_mp.multiply(rsx_mp)); + + BigDecimal p_parab_mp = psx_mp.multiply(psx_mp).add(psy_mp.multiply(psy_mp)); + BigDecimal q_parab_mp = qsx_mp.multiply(qsx_mp).add(qsy_mp.multiply(qsy_mp)); + BigDecimal r_parab_mp = rsx_mp.multiply(rsx_mp).add(rsy_mp.multiply(rsy_mp)); + + BigDecimal det_mp = (p_parab_mp.multiply(qr_det_mp).subtract(q_parab_mp.multiply(pr_det_mp))) + .add(r_parab_mp.multiply(pq_det_mp)); + + return det_mp.signum(); + } + + /** + * Calculates if the point s is inside of the circumcircle inscribed by the clockwise oriented triangle p-q-r. + * Returns 1 for outside, -1 for inside, and 0 for cocircular. + * Note that the convention used here differs from what is commonly found in literature, which can define the relation + * in terms of a counter-clockwise oriented circle, and this flips the sign (think of the signed volume of the tetrahedron). + * May use high precision arithmetics for some special cases. + */ + static int inCircleRobust(Point2D p, Point2D q, Point2D r, Point2D s) { + ECoordinate psx_ec = new ECoordinate(), psy_ec = new ECoordinate(); + psx_ec.set(p.x); + psx_ec.sub(s.x); + psy_ec.set(p.y); + psy_ec.sub(s.y); + + ECoordinate qsx_ec = new ECoordinate(), qsy_ec = new ECoordinate(); + qsx_ec.set(q.x); + qsx_ec.sub(s.x); + qsy_ec.set(q.y); + qsy_ec.sub(s.y); + + ECoordinate rsx_ec = new ECoordinate(), rsy_ec = new ECoordinate(); + rsx_ec.set(r.x); + rsx_ec.sub(s.x); + rsy_ec.set(r.y); + rsy_ec.sub(s.y); + + ECoordinate psx_ec_qsy_ec = new ECoordinate(); + psx_ec_qsy_ec.set(psx_ec); + psx_ec_qsy_ec.mul(qsy_ec); + ECoordinate psy_ec_qsx_ec = new ECoordinate(); + psy_ec_qsx_ec.set(psy_ec); + psy_ec_qsx_ec.mul(qsx_ec); + ECoordinate qsx_ec_rsy_ec = new ECoordinate(); + qsx_ec_rsy_ec.set(qsx_ec); + qsx_ec_rsy_ec.mul(rsy_ec); + ECoordinate qsy_ec_rsx_ec = new ECoordinate(); + qsy_ec_rsx_ec.set(qsy_ec); + qsy_ec_rsx_ec.mul(rsx_ec); + ECoordinate psx_ec_rsy_ec = new ECoordinate(); + psx_ec_rsy_ec.set(psx_ec); + psx_ec_rsy_ec.mul(rsy_ec); + ECoordinate psy_ec_rsx_ec = new ECoordinate(); + psy_ec_rsx_ec.set(psy_ec); + psy_ec_rsx_ec.mul(rsx_ec); + + ECoordinate pq_det_ec = new ECoordinate(); + pq_det_ec.set(psx_ec_qsy_ec); + pq_det_ec.sub(psy_ec_qsx_ec); + ECoordinate qr_det_ec = new ECoordinate(); + qr_det_ec.set(qsx_ec_rsy_ec); + qr_det_ec.sub(qsy_ec_rsx_ec); + ECoordinate pr_det_ec = new ECoordinate(); + pr_det_ec.set(psx_ec_rsy_ec); + pr_det_ec.sub(psy_ec_rsx_ec); + + ECoordinate psx_ec_psx_ec = new ECoordinate(); + psx_ec_psx_ec.set(psx_ec); + psx_ec_psx_ec.mul(psx_ec); + ECoordinate psy_ec_psy_ec = new ECoordinate(); + psy_ec_psy_ec.set(psy_ec); + psy_ec_psy_ec.mul(psy_ec); + ECoordinate qsx_ec_qsx_ec = new ECoordinate(); + qsx_ec_qsx_ec.set(qsx_ec); + qsx_ec_qsx_ec.mul(qsx_ec); + ECoordinate qsy_ec_qsy_ec = new ECoordinate(); + qsy_ec_qsy_ec.set(qsy_ec); + qsy_ec_qsy_ec.mul(qsy_ec); + ECoordinate rsx_ec_rsx_ec = new ECoordinate(); + rsx_ec_rsx_ec.set(rsx_ec); + rsx_ec_rsx_ec.mul(rsx_ec); + ECoordinate rsy_ec_rsy_ec = new ECoordinate(); + rsy_ec_rsy_ec.set(rsy_ec); + rsy_ec_rsy_ec.mul(rsy_ec); + + ECoordinate p_parab_ec = new ECoordinate(); + p_parab_ec.set(psx_ec_psx_ec); + p_parab_ec.add(psy_ec_psy_ec); + ECoordinate q_parab_ec = new ECoordinate(); + q_parab_ec.set(qsx_ec_qsx_ec); + q_parab_ec.add(qsy_ec_qsy_ec); + ECoordinate r_parab_ec = new ECoordinate(); + r_parab_ec.set(rsx_ec_rsx_ec); + r_parab_ec.add(rsy_ec_rsy_ec); + + p_parab_ec.mul(qr_det_ec); + q_parab_ec.mul(pr_det_ec); + r_parab_ec.mul(pq_det_ec); + + ECoordinate det_ec = new ECoordinate(); + det_ec.set(p_parab_ec); + det_ec.sub(q_parab_ec); + det_ec.add(r_parab_ec); + + if (!det_ec.isFuzzyZero()) { + double det_ec_value = det_ec.value(); + + if (det_ec_value < 0.0) + return -1; + + if (det_ec_value > 0.0) + return 1; + + return 0; + } + + return inCircleRobustMP_(p, q, r, s); + } + + private static Point2D calculateCenterFromThreePointsHelperMP_(Point2D from, Point2D mid_point, Point2D to) { + assert(!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); + BigDecimal mx = new BigDecimal(mid_point.x); + mx = mx.subtract(new BigDecimal(from.x)); + BigDecimal my = new BigDecimal(mid_point.y); + my = my.subtract(new BigDecimal(from.y)); + BigDecimal tx = new BigDecimal(to.x); + tx = tx.subtract(new BigDecimal(from.x)); + BigDecimal ty = new BigDecimal(to.y); + ty = ty.subtract(new BigDecimal(from.y)); + + BigDecimal d = mx.multiply(ty); + BigDecimal tmp = my.multiply(tx); + d = d.subtract(tmp); + + if (d.signum() == 0) { + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + d = d.multiply(new BigDecimal(2.0)); + + BigDecimal mx2 = mx.multiply(mx); + BigDecimal my2 = my.multiply(my); + BigDecimal m_norm2 = mx2.add(my2); + BigDecimal tx2 = tx.multiply(tx); + BigDecimal ty2 = ty.multiply(ty); + BigDecimal t_norm2 = tx2.add(ty2); + + BigDecimal xo = my.multiply(t_norm2); + tmp = ty.multiply(m_norm2); + xo = xo.subtract(tmp); + xo = xo.divide(d, BigDecimal.ROUND_HALF_EVEN); + + BigDecimal yo = mx.multiply(t_norm2); + tmp = tx.multiply(m_norm2); + yo = yo.subtract(tmp); + yo = yo.divide(d, BigDecimal.ROUND_HALF_EVEN); + + Point2D center = Point2D.construct(from.x - xo.doubleValue(), from.y + yo.doubleValue()); + return center; + } + + private static Point2D calculateCenterFromThreePointsHelper_(Point2D from, Point2D mid_point, Point2D to) { + assert(!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); + ECoordinate mx = new ECoordinate(mid_point.x); + mx.sub(from.x); + ECoordinate my = new ECoordinate(mid_point.y); + my.sub(from.y); + ECoordinate tx = new ECoordinate(to.x); + tx.sub(from.x); + ECoordinate ty = new ECoordinate(to.y); + ty.sub(from.y); + + ECoordinate d = new ECoordinate(mx); + d.mul(ty); + ECoordinate tmp = new ECoordinate(my); + tmp.mul(tx); + d.sub(tmp); + + if (d.value() == 0.0) { + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + d.mul(2.0); + + ECoordinate mx2 = new ECoordinate(mx); + mx2.mul(mx); + ECoordinate my2 = new ECoordinate(my); + my2.mul(my); + ECoordinate m_norm2 = new ECoordinate(mx2); + m_norm2.add(my2); + ECoordinate tx2 = new ECoordinate(tx); + tx2.mul(tx); + ECoordinate ty2 = new ECoordinate(ty); + ty2.mul(ty); + ECoordinate t_norm2 = new ECoordinate(tx2); + t_norm2.add(ty2); + + ECoordinate xo = new ECoordinate(my); + xo.mul(t_norm2); + tmp = new ECoordinate(ty); + tmp.mul(m_norm2); + xo.sub(tmp); + xo.div(d); + + ECoordinate yo = new ECoordinate(mx); + yo.mul(t_norm2); + tmp = new ECoordinate(tx); + tmp.mul(m_norm2); + yo.sub(tmp); + yo.div(d); + + Point2D center = Point2D.construct(from.x - xo.value(), from.y + yo.value()); + double r1 = Point2D.construct(from.x - center.x, from.y - center.y).length(); + double r2 = Point2D.construct(mid_point.x - center.x, mid_point.y - center.y).length(); + double r3 = Point2D.construct(to.x - center.x, to.y - center.y).length(); + double base = r1 + Math.abs(from.x) + Math.abs(mid_point.x) + Math.abs(to.x) + Math.abs(from.y) + + Math.abs(mid_point.y) + Math.abs(to.y); + + double tol = 1e-15; + if ((Math.abs(r1 - r2) <= base * tol && Math.abs(r1 - r3) <= base * tol)) + return center;//returns center value for MP_value type or when calculated radius value for from - center, mid - center, and to - center are very close. + + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + static Point2D calculateCircleCenterFromThreePoints(Point2D from, Point2D mid_point, Point2D to) { + if (from.isEqual(to) || from.isEqual(mid_point) || to.isEqual(mid_point)) { + return new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); + } + + Point2D pt = calculateCenterFromThreePointsHelper_(from, mid_point, to); //use error tracking calculations + if (pt.isNaN()) + return calculateCenterFromThreePointsHelperMP_(from, mid_point, to); //use precise calculations + else { + return pt; + } + } + @Override public int hashCode() { return NumberUtils.hash(NumberUtils.hash(x), y); } + double getAxis(int ordinate) { + assert(ordinate == 0 || ordinate == 1); + return (ordinate == 0 ? x : y); + } } diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index a8316ff3..849b00e1 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -42,11 +42,17 @@ public final class Point3D implements Serializable { public Point3D() { } + public Point3D(Point3D other) { + setCoords(other); + } + + public Point3D(double x, double y, double z) { + setCoords(x, y, z); + } + public static Point3D construct(double x, double y, double z) { Point3D pt = new Point3D(); - pt.x = x; - pt.y = y; - pt.z = z; + pt.setCoords(x, y, z); return pt; } @@ -56,6 +62,10 @@ public void setCoords(double x, double y, double z) { this.z = z; } + public void setCoords(Point3D other) { + setCoords(other.x, other.y, other.z); + } + public void setZero() { x = 0.0; y = 0.0; @@ -64,38 +74,62 @@ public void setZero() { public void normalize() { double len = length(); - if (len != 0) - return; + if (len == 0) { + x = 1.0; + y = 0.0; + z = 0.0; + } else { + x /= len; + y /= len; + z /= len; + } + } - x /= len; - y /= len; - z /= len; + public double dotProduct(Point3D other) { + return x * other.x + y * other.y + z * other.z; + } + + public double sqrLength() { + return x * x + y * y + z * z; } public double length() { return Math.sqrt(x * x + y * y + z * z); } - public Point3D(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; + public void sub(Point3D other) + { + x -= other.x; + y -= other.y; + z -= other.z; + } + + public void sub(Point3D p1, Point3D p2) { + x = p1.x - p2.x; + y = p1.y - p2.y; + z = p1.z - p2.z; } - public Point3D sub(Point3D other) { - return new Point3D(x - other.x, y - other.y, z - other.z); + public void scale(double f, Point3D other) { + x = f * other.x; + y = f * other.y; + z = f * other.z; } - public Point3D mul(double factor) { - return new Point3D(x * factor, y * factor, z * factor); + public void mul(double factor) { + x *= factor; + y *= factor; + z *= factor; } void _setNan() { x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + z = NumberUtils.NaN(); } boolean _isNan() { - return NumberUtils.isNaN(x); + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y) || NumberUtils.isNaN(z); } } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 6d2a97f4..cb027357 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -30,7 +30,7 @@ /** * A polygon is a collection of one or many interior or exterior rings. */ -public final class Polygon extends MultiPath implements Serializable { +public class Polygon extends MultiPath implements Serializable { private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and @@ -43,7 +43,7 @@ public Polygon() { m_impl = new MultiPathImpl(true); } - Polygon(VertexDescription vd) { + public Polygon(VertexDescription vd) { m_impl = new MultiPathImpl(true, vd); } diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index 26701d30..d56f2220 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -26,7 +26,7 @@ final class PolygonUtils { - enum PiPResult { + public enum PiPResult { PiPOutside, PiPInside, PiPBoundary }; @@ -34,7 +34,7 @@ enum PiPResult { /** * Tests if Point is inside the Polygon. Returns PiPOutside if not in * polygon, PiPInside if in the polygon, PiPBoundary is if on the border. It - * tests border only if the tolerance is > 0, otherwise PiPBoundary cannot + * tests border only if the tolerance is greater than 0, otherwise PiPBoundary cannot * be returned. Note: If the tolerance is not 0, the test is more expensive * because it calculates closest distance from a point to each segment. * @@ -79,7 +79,7 @@ static PiPResult isPointInPolygon2D(Polygon polygon, double inputPointXVal, /** * Tests if Point is inside the Polygon's ring. Returns PiPOutside if not in * ring, PiPInside if in the ring, PiPBoundary is if on the border. It tests - * border only if the tolerance is > 0, otherwise PiPBoundary cannot be + * border only if the tolerance is greater than 0, otherwise PiPBoundary cannot be * returned. Note: If the tolerance is not 0, the test is more expensive * because it calculates closest distance from a point to each segment. * @@ -127,33 +127,10 @@ public static PiPResult isPointInAnyOuterRing(Polygon polygon, // internal and external boundaries. } - // #ifndef DOTNET - // /** - // *Tests point is inside the Polygon for an array of points. - // *Returns PiPOutside if not in polygon, PiPInside if in the polygon, - // PiPBoundary is if on the border. - // *It tests border only if the tolerance is > 0, otherwise PiPBoundary - // cannot be returned. - // *Note: If the tolerance is not 0, the test is more expensive. - // * - // *O(n*m) complexity, where n is the number of polygon segments, m is the - // number of input points. - // */ - // static void TestPointsInPolygon2D(Polygon polygon, const Point2D* - // inputPoints, int count, double tolerance, PiPResult testResults) - // { - // LOCALREFCLASS2(Array, Point2D*, int, inputPointsArr, - // const_cast(inputPoints), count); - // LOCALREFCLASS2(Array, PolygonUtils::PiPResult*, - // int, testResultsArr, testResults, count); - // TestPointsInPolygon2D(polygon, inputPointsArr, count, tolerance, - // testResultsArr); - // } - // #endif /** * Tests point is inside the Polygon for an array of points. Returns * PiPOutside if not in polygon, PiPInside if in the polygon, PiPBoundary is - * if on the border. It tests border only if the tolerance is > 0, otherwise + * if on the border. It tests border only if the tolerance is greater than 0, otherwise * PiPBoundary cannot be returned. Note: If the tolerance is not 0, the test * is more expensive. * @@ -182,31 +159,11 @@ static void testPointsInPolygon2D(Polygon polygon, double[] xyStreamBuffer, xyStreamBuffer[i * 2 + 1], tolerance); } - // public static void testPointsInPolygon2D(Polygon polygon, Geometry geom, - // int count, double tolerance, PiPResult[] testResults) - // { - // if(geom.getType() == Type.Point) - // { - // - // } - // else if(Geometry.isMultiVertex(geom.getType())) - // { - // - // } - // - // - // if (inputPoints.length < count || testResults.length < count) - // throw new IllegalArgumentException();//GEOMTHROW(invalid_argument); - // - // for (int i = 0; i < count; i++) - // testResults[i] = isPointInPolygon2D(polygon, inputPoints[i], tolerance); - // } - /** * Tests point is inside an Area Geometry (Envelope, Polygon) for an array * of points. Returns PiPOutside if not in area, PiPInside if in the area, * PiPBoundary is if on the border. It tests border only if the tolerance is - * > 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is + * greater than 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is * not 0, the test is more expensive. * * O(n*m) complexity, where n is the number of polygon segments, m is the diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index b95e9f81..4c83a147 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -31,7 +31,7 @@ * A polyline is a collection of one or many paths. * */ -public final class Polyline extends MultiPath implements Serializable { +public class Polyline extends MultiPath implements Serializable { private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and @@ -44,7 +44,7 @@ public Polyline() { m_impl = new MultiPathImpl(false); } - Polyline(VertexDescription vd) { + public Polyline(VertexDescription vd) { m_impl = new MultiPathImpl(false, vd); } diff --git a/src/main/java/com/esri/core/geometry/PolylinePath.java b/src/main/java/com/esri/core/geometry/PolylinePath.java deleted file mode 100644 index 6d3342da..00000000 --- a/src/main/java/com/esri/core/geometry/PolylinePath.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -import java.util.Comparator; - -class PolylinePath { - Point2D m_fromPoint; - Point2D m_toPoint; - double m_fromDist; // from lower left corner; -1.0 if point is not on - // clipping bounday - double m_toDist; // from lower left corner; -1.0 if point is not on clipping - // bounday - int m_path; // from polyline - boolean m_used; - - public PolylinePath() { - } - - public PolylinePath(Point2D fromPoint, Point2D toPoint, double fromDist, - double toDist, int path) { - m_fromPoint = fromPoint; - m_toPoint = toPoint; - m_fromDist = fromDist; - m_toDist = toDist; - m_path = path; - m_used = false; - } - - void setValues(Point2D fromPoint, Point2D toPoint, double fromDist, - double toDist, int path) { - m_fromPoint = fromPoint; - m_toPoint = toPoint; - m_fromDist = fromDist; - m_toDist = toDist; - m_path = path; - m_used = false; - } - - // to be used in Use SORTARRAY - -} - -class PolylinePathComparator implements Comparator { - @Override - public int compare(PolylinePath v1, PolylinePath v2) { - if ((v1).m_fromDist < (v2).m_fromDist) - return -1; - else if ((v1).m_fromDist > (v2).m_fromDist) - return 1; - else - return 0; - } - -} diff --git a/src/main/java/com/esri/core/geometry/PtSrlzr.java b/src/main/java/com/esri/core/geometry/PtSrlzr.java new file mode 100644 index 00000000..68dd1aae --- /dev/null +++ b/src/main/java/com/esri/core/geometry/PtSrlzr.java @@ -0,0 +1,88 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for Point +public class PtSrlzr implements Serializable { + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; + + public Object readResolve() throws ObjectStreamException { + Point point = null; + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + point = new Point(vd); + if (attribs != null) { + point.setXY(attribs[0], attribs[1]); + int index = 2; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + point.setAttribute(semantics, ord, attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + + return point; + } + + public void setGeometryByValue(Point point) throws ObjectStreamException { + try { + attribs = null; + if (point == null) { + descriptionBitMask = 1; + } + + VertexDescription vd = point.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + if (point.isEmpty()) { + return; + } + + attribs = new double[vd.getTotalComponentCount()]; + attribs[0] = point.getX(); + attribs[1] = point.getY(); + int index = 2; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + attribs[index++] = point.getAttributeAsDbl(semantics, ord); + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index 9f6be163..32d81c3c 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -28,24 +28,31 @@ public class QuadTree { public static final class QuadTreeIterator { /** - * Resets the iterator to an starting state on the Quad_tree. If the + * Resets the iterator to an starting state on the QuadTree. If the * input Geometry is a Line segment, then the query will be the segment. - * Otherwise the query will be the Envelope_2D bounding the Geometry. - * \param query The Geometry used for the query. \param tolerance The - * tolerance used for the intersection tests. + * Otherwise the query will be the Envelope2D bounding the Geometry. + * \param query The Geometry used for the query. + * \param tolerance The tolerance used for the intersection tests. */ public void resetIterator(Geometry query, double tolerance) { - m_impl.resetIterator(query, tolerance); + if (!m_b_sorted) + ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); + else + ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); } /** - * Resets the iterator to a starting state on the Quad_tree using the - * input Envelope_2D as the query. \param query The Envelope_2D used for - * the query. \param tolerance The tolerance used for the intersection + * Resets the iterator to a starting state on the QuadTree using the + * input Envelope2D as the query. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection * tests. */ public void resetIterator(Envelope2D query, double tolerance) { - m_impl.resetIterator(query, tolerance); + if (!m_b_sorted) + ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); + else + ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); } /** @@ -53,7 +60,10 @@ public void resetIterator(Envelope2D query, double tolerance) { * Element_handle. */ public int next() { - return m_impl.next(); + if (!m_b_sorted) + return ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).next(); + else + return ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).next(); } /** @@ -63,136 +73,260 @@ Object getImpl_() { return m_impl; } - // Creates an iterator on the input Quad_tree_impl. The query will be - // the Envelope_2D bounding the input Geometry. - private QuadTreeIterator(Object obj) { - m_impl = (QuadTreeImpl.QuadTreeIteratorImpl) obj; + // Creates an iterator on the input QuadTreeImpl. The query will be + // the Envelope2D bounding the input Geometry. + private QuadTreeIterator(Object obj, boolean bSorted) { + + m_impl = obj; + m_b_sorted = bSorted; } - private QuadTreeImpl.QuadTreeIteratorImpl m_impl; - }; + private Object m_impl; + private boolean m_b_sorted; + } /** - * Creates a Quad_tree with the root having the extent of the input - * Envelope_2D, and height of the input height, where the root starts at - * height 0. Note that the height cannot be larger than 16 if on a 32 bit - * platform and 32 if on a 64 bit platform. \param extent The extent of the - * Quad_tree. \param height The max height of the Quad_tree. + * Creates a QuadTree with the root having the extent of the input + * Envelope2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the QuadTree. + * \param height The max height of the QuadTree. */ public QuadTree(Envelope2D extent, int height) { m_impl = new QuadTreeImpl(extent, height); } /** - * Inserts the element and bounding_box into the Quad_tree. Note that a copy + * Creates a QuadTree with the root having the extent of the input Envelope2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the QuadTreeImpl. + * \param height The max height of the QuadTreeImpl. + * \param bStoreDuplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it.. + */ + public QuadTree(Envelope2D extent, int height, boolean bStoreDuplicates) { + m_impl = new QuadTreeImpl(extent, height, bStoreDuplicates); + } + + /** + * Inserts the element and bounding_box into the QuadTree. Note that a copy * will me made of the input bounding_box. Note that this will invalidate - * any active iterator on the Quad_tree. Returns an Element_handle - * corresponding to the element and bounding_box. \param element The element - * of the Geometry to be inserted. \param bounding_box The bounding_box of + * any active iterator on the QuadTree. Returns an Element_handle + * corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of * the Geometry to be inserted. */ - public int insert(int element, Envelope2D bounding_box) { - return m_impl.insert(element, bounding_box); + public int insert(int element, Envelope2D boundingBox) { + return m_impl.insert(element, boundingBox); } /** - * Inserts the element and bounding_box into the Quad_tree at the given + * Inserts the element and bounding_box into the QuadTree at the given * quad_handle. Note that a copy will me made of the input bounding_box. - * Note that this will invalidate any active iterator on the Quad_tree. + * Note that this will invalidate any active iterator on the QuadTree. * Returns an Element_handle corresponding to the element and bounding_box. - * \param element The element of the Geometry to be inserted. \param - * bounding_box The bounding_box of the Geometry to be inserted. \param - * hint_index A handle used as a hint where to place the element. This can + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. + * \param hint_index A handle used as a hint where to place the element. This can * be a handle obtained from a previous insertion and is useful on data * having strong locality such as segments of a Polygon. */ - public int insert(int element, Envelope2D bounding_box, int hint_index) { - return m_impl.insert(element, bounding_box, hint_index); + public int insert(int element, Envelope2D boundingBox, int hintIndex) { + return m_impl.insert(element, boundingBox, hintIndex); } /** * Removes the element and bounding_box at the given element_handle. Note - * that this will invalidate any active iterator on the Quad_tree. \param - * element_handle The handle corresponding to the element and bounding_box + * that this will invalidate any active iterator on the QuadTree. + * \param element_handle The handle corresponding to the element and bounding_box * to be removed. */ - public void removeElement(int element_handle) { - m_impl.removeElement(element_handle); + public void removeElement(int elementHandle) { + m_impl.removeElement(elementHandle); + } + + /** + * Returns the element at the given element_handle. + * \param element_handle The handle corresponding to the element to be retrieved. + */ + public int getElement(int elementHandle) { + return m_impl.getElement(elementHandle); + } + + /** + * Returns the element extent at the given element_handle. + * \param element_handle The handle corresponding to the element extent to be retrieved. + */ + public Envelope2D getElementExtent(int elementHandle) { + return m_impl.getElementExtent(elementHandle); + } + + /** + * Returns the extent of all elements in the quad tree. + */ + public Envelope2D getDataExtent() { + return m_impl.getDataExtent(); + } + + /** + * Returns the extent of the quad tree. + */ + public Envelope2D getQuadTreeExtent() { + return m_impl.getQuadTreeExtent(); + } + + /** + * Returns the number of elements in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public int getSubTreeElementCount(int quadHandle) { + return m_impl.getSubTreeElementCount(quadHandle); + } + + /** + * Returns the number of elements contained in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public int getContainedSubTreeElementCount(int quadHandle) { + return m_impl.getContainedSubTreeElementCount(quadHandle); + } + + /** + * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. + */ + public int getIntersectionCount(Envelope2D query, double tolerance, int maxCount) { + return m_impl.getIntersectionCount(query, tolerance, maxCount); } /** - * Returns the element at the given element_handle. \param element_handle - * The handle corresponding to the element to be retrieved. + * Returns true if the quad tree has data intersecting the given query. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. */ - public int getElement(int element_handle) { - return m_impl.getElement(element_handle); + public boolean hasData(Envelope2D query, double tolerance) { + return m_impl.hasData(query, tolerance); } /** * Returns the height of the quad at the given quad_handle. \param * quad_handle The handle corresponding to the quad. */ - public int getHeight(int quad_handle) { - return m_impl.getHeight(quad_handle); + public int getHeight(int quadHandle) { + return m_impl.getHeight(quadHandle); } /** - * Returns the extent of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. + * Returns the max height the quad tree can grow to. */ - public Envelope2D getExtent(int quad_handle) { - return m_impl.getExtent(quad_handle); + public int getMaxHeight() { + return m_impl.getMaxHeight(); + } + + /** + * Returns the extent of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public Envelope2D getExtent(int quadHandle) { + return m_impl.getExtent(quadHandle); } /** * Returns the Quad_handle of the quad containing the given element_handle. * \param element_handle The handle corresponding to the element. */ - public int getQuad(int element_handle) { - return m_impl.getQuad(element_handle); + public int getQuad(int elementHandle) { + return m_impl.getQuad(elementHandle); } /** - * Returns the number of elements in the Quad_tree. + * Returns the number of elements in the QuadTree. */ public int getElementCount() { return m_impl.getElementCount(); } /** - * Gets an iterator on the Quad_tree. The query will be the Envelope_2D that + * Gets an iterator on the QuadTree. The query will be the Envelope2D that * bounds the input Geometry. To reuse the existing iterator on the same - * Quad_tree but with a new query, use the reset_iterator function on the - * Quad_tree_iterator. \param query The Geometry used for the query. If the + * QuadTree but with a new query, use the reset_iterator function on the + * QuadTree_iterator. + * \param query The Geometry used for the query. If the * Geometry is a Line segment, then the query will be the segment. Otherwise - * the query will be the Envelope_2D bounding the Geometry. \param tolerance - * The tolerance used for the intersection tests. + * the query will be the Envelope2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. */ public QuadTreeIterator getIterator(Geometry query, double tolerance) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, - tolerance); - return new QuadTreeIterator(iterator); + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); } /** - * Gets an iterator on the Quad_tree using the input Envelope_2D as the - * query. To reuse the existing iterator on the same Quad_tree but with a - * new query, use the reset_iterator function on the Quad_tree_iterator. - * \param query The Envelope_2D used for the query. \param tolerance The - * tolerance used for the intersection tests. + * Gets an iterator on the QuadTree using the input Envelope2D as the + * query. To reuse the existing iterator on the same QuadTree but with a + * new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. */ public QuadTreeIterator getIterator(Envelope2D query, double tolerance) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, - tolerance); - return new QuadTreeIterator(iterator); + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); } /** - * Gets an iterator on the Quad_tree. + * Gets an iterator on the QuadTree. */ public QuadTreeIterator getIterator() { QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); - return new QuadTreeIterator(iterator); + return new QuadTreeIterator(iterator, false); + } + + /** + * Gets an iterator on the QuadTree. The query will be the Envelope2D that bounds the input Geometry. + * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(Geometry query, double tolerance, boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Gets an iterator on the QuadTree using the input Envelope2D as the query. + * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(Envelope2D query, double tolerance, boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Gets an iterator on the QuadTree. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(); + return new QuadTreeIterator(iterator, true); + } } /** diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index cbef824b..fe48999a 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -42,7 +42,7 @@ void resetIterator(Geometry query, double tolerance) { query.queryLooseEnvelope2D(m_query_box); m_query_box.inflate(tolerance, tolerance); - if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { + if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { int type = query.getType().value(); m_b_linear = Geometry.isSegment(type); @@ -57,8 +57,7 @@ void resetIterator(Geometry query, double tolerance) { m_quads_stack.add(m_quad_tree.m_root); m_extents_stack.add(m_quad_tree.m_extent); - m_next_element_handle = m_quad_tree - .getFirstElement_(m_quad_tree.m_root); + m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); } else m_next_element_handle = -1; } @@ -77,11 +76,10 @@ void resetIterator(Envelope2D query, double tolerance) { m_query_box.inflate(tolerance, tolerance); m_tolerance = NumberUtils.NaN(); // we don't need it - if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { + if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { m_quads_stack.add(m_quad_tree.m_root); m_extents_stack.add(m_quad_tree.m_extent); - m_next_element_handle = m_quad_tree - .getFirstElement_(m_quad_tree.m_root); + m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); m_b_linear = false; } else m_next_element_handle = -1; @@ -104,7 +102,7 @@ int next() { Envelope2D extent_inf = null; Envelope2D[] child_extents = null; - if (m_b_linear) {// Should this memory be cached for reuse? + if (m_b_linear) { start = new Point2D(); end = new Point2D(); extent_inf = new Envelope2D(); @@ -113,10 +111,8 @@ int next() { boolean b_found_hit = false; while (!b_found_hit) { while (m_current_element_handle != -1) { - int current_box_handle = m_quad_tree - .getBoxHandle_(m_current_element_handle); - bounding_box = m_quad_tree - .getBoundingBox_(current_box_handle); + int current_data_handle = m_quad_tree.get_data_(m_current_element_handle); + bounding_box = m_quad_tree.get_bounding_box_value_(current_data_handle); if (bounding_box.isIntersecting(m_query_box)) { if (m_b_linear) { @@ -136,21 +132,14 @@ int next() { } // get next element_handle - m_current_element_handle = m_quad_tree - .getNextElement_(m_current_element_handle); + m_current_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); } - // If m_current_element_handle equals -1, then we've exhausted - // our search in the current quadtree node + // If m_current_element_handle equals -1, then we've exhausted our search in the current quadtree node if (m_current_element_handle == -1) { - // get the last node from the stack and add the children - // whose extent intersects m_query_box + // get the last node from the stack and add the children whose extent intersects m_query_box int current_quad = m_quads_stack.getLast(); - Envelope2D current_extent = m_extents_stack - .get(m_extents_stack.size() - 1); - - double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); - double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + Envelope2D current_extent = m_extents_stack.get(m_extents_stack.size() - 1); if (child_extents == null) { child_extents = new Envelope2D[4]; @@ -160,38 +149,30 @@ int next() { child_extents[3] = new Envelope2D(); } - setChildExtents_(current_extent, child_extents); + set_child_extents_(current_extent, child_extents); m_quads_stack.removeLast(); m_extents_stack.remove(m_extents_stack.size() - 1); for (int quadrant = 0; quadrant < 4; quadrant++) { - int child_handle = m_quad_tree.getChild_(current_quad, - quadrant); - - if (child_handle != -1 - && m_quad_tree - .getSubTreeElementCount(child_handle) > 0) { - if (child_extents[quadrant] - .isIntersecting(m_query_box)) { + int child_handle = m_quad_tree.get_child_(current_quad, quadrant); + + if (child_handle != -1 && m_quad_tree.getSubTreeElementCount(child_handle) > 0) { + if (child_extents[quadrant].isIntersecting(m_query_box)) { if (m_b_linear) { start.setCoords(m_query_start); end.setCoords(m_query_end); - extent_inf - .setCoords(child_extents[quadrant]); - extent_inf - .inflate(m_tolerance, m_tolerance); + extent_inf.setCoords(child_extents[quadrant]); + extent_inf.inflate(m_tolerance, m_tolerance); if (extent_inf.clipLine(start, end) > 0) { Envelope2D child_extent = new Envelope2D(); - child_extent - .setCoords(child_extents[quadrant]); + child_extent.setCoords(child_extents[quadrant]); m_quads_stack.add(child_handle); m_extents_stack.add(child_extent); } } else { Envelope2D child_extent = new Envelope2D(); - child_extent - .setCoords(child_extents[quadrant]); + child_extent.setCoords(child_extents[quadrant]); m_quads_stack.add(child_handle); m_extents_stack.add(child_extent); } @@ -204,24 +185,20 @@ int next() { if (m_quads_stack.size() == 0) return -1; - m_current_element_handle = m_quad_tree - .getFirstElement_(m_quads_stack.get(m_quads_stack - .size() - 1)); + m_current_element_handle = m_quad_tree.get_first_element_(m_quads_stack.get(m_quads_stack.size() - 1)); } } // We did not exhaust our search in the current node, so we return // the element at m_current_element_handle in m_element_nodes - m_next_element_handle = m_quad_tree - .getNextElement_(m_current_element_handle); + m_next_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); return m_current_element_handle; } // Creates an iterator on the input Quad_tree_impl. The query will be // the Envelope_2D bounding the input Geometry. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, - double tolerance) { + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, double tolerance) { m_quad_tree = quad_tree_impl; m_query_box = new Envelope2D(); m_quads_stack = new AttributeStreamOfInt32(0); @@ -231,8 +208,7 @@ int next() { // Creates an iterator on the input Quad_tree_impl using the input // Envelope_2D as the query. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, - double tolerance) { + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, double tolerance) { m_quad_tree = quad_tree_impl; m_query_box = new Envelope2D(); m_quads_stack = new AttributeStreamOfInt32(0); @@ -257,147 +233,346 @@ int next() { private int m_next_element_handle; private QuadTreeImpl m_quad_tree; private AttributeStreamOfInt32 m_quads_stack; - private ArrayList m_extents_stack; // this won't grow bigger - // than 4 * - // (m_quad_tree->m_height - // - 1) + private ArrayList m_extents_stack; // this won't grow bigger than 4 * (m_quad_tree->m_height - 1) + } + + static final class QuadTreeSortedIteratorImpl { + /** + * Resets the iterator to a starting state on the Quad_tree_impl. If the input Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param query The Geometry used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Geometry query, double tolerance) { + m_quad_tree_iterator_impl.resetIterator(query, tolerance); + m_sorted_handles.resize(0); + m_index = -1; + } + + /** + * Resets the iterator to a starting state on the Quad_tree_impl using the input Envelope_2D as the query. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Envelope2D query, double tolerance) { + m_quad_tree_iterator_impl.resetIterator(query, tolerance); + m_sorted_handles.resize(0); + m_index = -1; + } + + /** + * Moves the iterator to the next Element_handle and returns the Element_handle. + */ + int next() { + if (m_index == -1) { + int element_handle = -1; + while ((element_handle = m_quad_tree_iterator_impl.next()) != -1) + m_sorted_handles.add(element_handle); + + m_bucket_sort.sort(m_sorted_handles, 0, m_sorted_handles.size(), new Sorter(m_quad_tree_iterator_impl.m_quad_tree)); + } + + if (m_index == m_sorted_handles.size() - 1) + return -1; + + m_index++; + return m_sorted_handles.get(m_index); + } + + //Creates a sorted iterator on the input Quad_tree_iterator_impl + QuadTreeSortedIteratorImpl(QuadTreeIteratorImpl quad_tree_iterator_impl) { + m_bucket_sort = new BucketSort(); + m_sorted_handles = new AttributeStreamOfInt32(0); + m_quad_tree_iterator_impl = quad_tree_iterator_impl; + m_index = -1; + } + + private class Sorter extends ClassicSort { + public Sorter(QuadTreeImpl quad_tree) { + m_quad_tree = quad_tree; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.sort(begin, end); + } + + @Override + public double getValue(int e) { + return m_quad_tree.getElement(e); + } + + private QuadTreeImpl m_quad_tree; + } + + private BucketSort m_bucket_sort; + private AttributeStreamOfInt32 m_sorted_handles; + private QuadTreeIteratorImpl m_quad_tree_iterator_impl; + int m_index; } /** - * Creates a Quad_tree_impl with the root having the extent of the input - * Envelope_2D, and height of the input height, where the root starts at - * height 0. Note that the height cannot be larger than 16 if on a 32 bit - * platform and 32 if on a 64 bit platform. \param extent The extent of the - * Quad_tree_impl. \param height The max height of the Quad_tree_impl. + * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. */ QuadTreeImpl(Envelope2D extent, int height) { - m_quad_tree_nodes = new StridedIndexTypeCollection(11); - m_element_nodes = new StridedIndexTypeCollection(5); - m_boxes = new ArrayList(0); - m_free_boxes = new AttributeStreamOfInt32(0); + m_quad_tree_nodes = new StridedIndexTypeCollection(10); + m_element_nodes = new StridedIndexTypeCollection(4); + m_data = new ArrayList(0); + m_free_data = new AttributeStreamOfInt32(0); + m_b_store_duplicates = false; + + m_extent = new Envelope2D(); + m_data_extent = new Envelope2D(); + + reset_(extent, height); + } + + /** + * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. + * \param b_store_duplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it. + */ + QuadTreeImpl(Envelope2D extent, int height, boolean b_store_duplicates) { + m_quad_tree_nodes = (b_store_duplicates ? new StridedIndexTypeCollection(11) : new StridedIndexTypeCollection(10)); + m_element_nodes = new StridedIndexTypeCollection(4); + m_data = new ArrayList(0); + m_free_data = new AttributeStreamOfInt32(0); + m_b_store_duplicates = b_store_duplicates; + m_extent = new Envelope2D(); + m_data_extent = new Envelope2D(); + reset_(extent, height); } /** - * Resets the Quad_tree_impl to the given extent and height. \param extent - * The extent of the Quad_tree_impl. \param height The max height of the - * Quad_tree_impl. + * Resets the Quad_tree_impl to the given extent and height. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. */ void reset(Envelope2D extent, int height) { m_quad_tree_nodes.deleteAll(false); m_element_nodes.deleteAll(false); - m_boxes.clear(); - m_free_boxes.clear(false); + m_data.clear(); + m_free_data.clear(false); reset_(extent, height); } /** - * Inserts the element and bounding_box into the Quad_tree_impl. Note that - * this will invalidate any active iterator on the Quad_tree_impl. Returns - * an int corresponding to the element and bounding_box. \param element The - * element of the Geometry to be inserted. \param bounding_box The - * bounding_box of the Geometry to be inserted. + * Inserts the element and bounding_box into the Quad_tree_impl. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. */ int insert(int element, Envelope2D bounding_box) { - return insert_(element, bounding_box, 0, m_extent, m_root, false, -1); + if (m_root == -1) + create_root_(); + + if (m_b_store_duplicates) { + int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (success != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return success; + } + + int element_handle = insert_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (element_handle != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return element_handle; } /** - * Inserts the element and bounding_box into the Quad_tree_impl at the given - * quad_handle. Note that this will invalidate any active iterator on the - * Quad_tree_impl. Returns an int corresponding to the element and - * bounding_box. \param element The element of the Geometry to be inserted. + * Inserts the element and bounding_box into the Quad_tree_impl at the given quad_handle. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. * \param bounding_box The bounding_box of the Geometry to be inserted. - * \param hint_index A handle used as a hint where to place the element. - * This can be a handle obtained from a previous insertion and is useful on - * data having strong locality such as segments of a Polygon. + * \param hint_index A handle used as a hint where to place the element. This can be a handle obtained from a previous insertion and is useful on data having strong locality such as segments of a Polygon. */ int insert(int element, Envelope2D bounding_box, int hint_index) { + if (m_root == -1) + create_root_(); + + if (m_b_store_duplicates) { + int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (success != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + return success; + } + int quad_handle; if (hint_index == -1) quad_handle = m_root; else - quad_handle = getQuad_(hint_index); + quad_handle = get_quad_(hint_index); int quad_height = getHeight(quad_handle); Envelope2D quad_extent = getExtent(quad_handle); - return insert_(element, bounding_box, quad_height, quad_extent, - quad_handle, false, -1); + + int element_handle = insert_(element, bounding_box, quad_height, quad_extent, quad_handle, false, -1); + + if (element_handle != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return element_handle; } /** - * Removes the element and bounding_box at the given element_handle. Note - * that this will invalidate any active iterator on the Quad_tree_impl. - * \param element_handle The handle corresponding to the element and - * bounding_box to be removed. + * Removes the element and bounding_box at the given element_handle. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * \param element_handle The handle corresponding to the element and bounding_box to be removed. */ void removeElement(int element_handle) { - int quad_handle = getQuad_(element_handle); - int nextElementHandle = disconnectElementHandle_(element_handle); - freeElementAndBoxNode_(element_handle); + if (m_b_store_duplicates) + throw new GeometryException("invalid call"); + + int quad_handle = get_quad_(element_handle); + disconnect_element_handle_(element_handle); + free_element_and_box_node_(element_handle); + + int q = quad_handle; + + while (q != -1) { + set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) - 1); + int parent = get_parent_(q); + + if (get_sub_tree_element_count_(q) == 0) { + assert (get_local_element_count_(q) == 0); + + if (q != m_root) { + int quadrant = get_quadrant_(q); + m_quad_tree_nodes.deleteElement(q); + set_child_(parent, quadrant, -1); + } + } - for (int q = quad_handle; q != -1; q = getParent_(q)) { - setSubTreeElementCount_(q, getSubTreeElementCount_(q) - 1); - assert (getSubTreeElementCount_(q) >= 0); + q = parent; } } /** * Returns the element at the given element_handle. - * \param element_handle - * The handle corresponding to the element to be retrieved. + * \param element_handle The handle corresponding to the element to be retrieved. */ int getElement(int element_handle) { - return getElement_(element_handle); + return get_element_value_(get_data_(element_handle)); } + /** + * Returns the ith unique element. + * \param i The index corresponding to the ith unique element. + */ + int getElementAtIndex(int i) { + return m_data.get(i).element; + } - /** - * Returns a reference to the element extent at the given element_handle. - * \param element_handle - * The handle corresponding to the element to be retrieved. - */ - Envelope2D getElementExtent(int element_handle) - { - int box_handle = getBoxHandle_(element_handle); - return getBoundingBox_(box_handle); - } + /** + * Returns the element extent at the given element_handle. + * \param element_handle The handle corresponding to the element extent to be retrieved. + */ + Envelope2D getElementExtent(int element_handle) { + int data_handle = get_data_(element_handle); + return get_bounding_box_value_(data_handle); + } /** - * Returns the height of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. + * Returns the extent of the ith unique element. + * \param i The index corresponding to the ith unique element. + */ + Envelope2D getElementExtentAtIndex(int i) { + return m_data.get(i).box; + } + + /** + * Returns the extent of all elements in the quad tree. + */ + Envelope2D getDataExtent() { + return m_data_extent; + } + + /** + * Returns the extent of the quad tree. + */ + Envelope2D getQuadTreeExtent() { + return m_extent; + } + + /** + * Returns the height of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. */ int getHeight(int quad_handle) { - return getHeight_(quad_handle); + return get_height_(quad_handle); + } + + int getMaxHeight() { + return m_height; } /** - * Returns the extent of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. + * Returns the extent of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. */ Envelope2D getExtent(int quad_handle) { Envelope2D quad_extent = new Envelope2D(); quad_extent.setCoords(m_extent); - int height = getHeight_(quad_handle); - int morten_number = getMortenNumber_(quad_handle); - int mask = 3; + if (quad_handle == m_root) + return quad_extent; + + AttributeStreamOfInt32 quadrants = new AttributeStreamOfInt32(0); - for (int i = 0; i < 2 * height; i += 2) { - int child = (int) (mask & (morten_number >> i)); + int q = quad_handle; - if (child == 0) {// northeast + do { + quadrants.add(get_quadrant_(q)); + q = get_parent_(q); + + } while (q != m_root); + + int sz = quadrants.size(); + assert (sz == getHeight(quad_handle)); + + for (int i = 0; i < sz; i++) { + int child = quadrants.getLast(); + quadrants.removeLast(); + + if (child == 0) {//northeast quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else if (child == 1) {// northwest + } else if (child == 1) {//northwest quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else if (child == 2) {// southwest + } else if (child == 2) {//southwest quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else {// southeast + } else {//southeast quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); } @@ -407,26 +582,134 @@ Envelope2D getExtent(int quad_handle) { } /** - * Returns the int of the quad containing the given element_handle. \param - * element_handle The handle corresponding to the element. + * Returns the Quad_handle of the quad containing the given element_handle. + * \param element_handle The handle corresponding to the element. */ int getQuad(int element_handle) { - return getQuad_(element_handle); + return get_quad_(element_handle); } /** * Returns the number of elements in the Quad_tree_impl. */ int getElementCount() { - return getSubTreeElementCount_(m_root); + if (m_root == -1) + return 0; + + assert (get_sub_tree_element_count_(m_root) == m_data.size()); + return get_sub_tree_element_count_(m_root); } /** - * Returns the number of elements in the subtree rooted at the given - * quad_handle. \param quad_handle The handle corresponding to the quad. + * Returns the number of elements in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. */ int getSubTreeElementCount(int quad_handle) { - return getSubTreeElementCount_(quad_handle); + return get_sub_tree_element_count_(quad_handle); + } + + /** + * Returns the number of elements contained in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + int getContainedSubTreeElementCount(int quad_handle) { + if (!m_b_store_duplicates) + return get_sub_tree_element_count_(quad_handle); + + return get_contained_sub_tree_element_count_(quad_handle); + } + + /** + * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. + */ + int getIntersectionCount(Envelope2D query, double tolerance, int max_count) { + if (m_root == -1) + return 0; + + Envelope2D query_inflated = new Envelope2D(); + query_inflated.setCoords(query); + query_inflated.inflate(tolerance, tolerance); + + AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); + ArrayList extents_stack = new ArrayList(0); + quads_stack.add(m_root); + extents_stack.add(new Envelope2D(m_extent.xmin, m_extent.ymin, m_extent.xmax, m_extent.ymax)); + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + Envelope2D current_extent = new Envelope2D(); + + int intersection_count = 0; + + while (quads_stack.size() > 0) { + boolean b_subdivide = false; + + int current_quad_handle = quads_stack.getLast(); + current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); + + quads_stack.removeLast(); + extents_stack.remove(extents_stack.size() - 1); + + + if (query_inflated.contains(current_extent)) { + intersection_count += getSubTreeElementCount(current_quad_handle); + + if (max_count > 0 && intersection_count >= max_count) + return max_count; + } else { + if (query_inflated.isIntersecting(current_extent)) { + for (int element_handle = get_first_element_(current_quad_handle); element_handle != -1; element_handle = get_next_element_(element_handle)) { + int data_handle = get_data_(element_handle); + Envelope2D env = get_bounding_box_value_(data_handle); + + if (env.isIntersecting(query_inflated)) { + intersection_count++; + + if (max_count > 0 && intersection_count >= max_count) + return max_count; + } + } + + b_subdivide = getHeight(current_quad_handle) + 1 <= m_height; + } + } + + if (b_subdivide) { + set_child_extents_(current_extent, child_extents); + + for (int i = 0; i < 4; i++) { + int child_handle = get_child_(current_quad_handle, i); + + if (child_handle != -1 && getSubTreeElementCount(child_handle) > 0) { + boolean b_is_intersecting = query_inflated.isIntersecting(child_extents[i]); + + if (b_is_intersecting) { + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + } + } + } + } + } + + return intersection_count; + } + + /** + * Returns true if the quad tree has data intersecting the given query. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + boolean hasData(Envelope2D query, double tolerance) { + int count = getIntersectionCount(query, tolerance, 1); + return count >= 1; } /** @@ -460,37 +743,58 @@ QuadTreeIteratorImpl getIterator() { return new QuadTreeIteratorImpl(this); } + /** + * Gets a sorted iterator on the Quad_tree_impl. The Element_handles will be returned in increasing order of their corresponding Element_types. + * The query will be the Envelope_2D that bounds the input Geometry. + * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_sorted_iterator_impl. + * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeSortedIteratorImpl getSortedIterator(Geometry query, double tolerance) { + return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); + } + + /** + * Gets a sorted iterator on the Quad_tree_impl using the input Envelope_2D as the query. The Element_handles will be returned in increasing order of their corresponding Element_types. + * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_iterator_impl. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeSortedIteratorImpl getSortedIterator(Envelope2D query, double tolerance) { + return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); + } + + /** + * Gets a sorted iterator on the Quad_tree. The Element_handles will be returned in increasing order of their corresponding Element_types + */ + QuadTreeSortedIteratorImpl getSortedIterator() { + return new QuadTreeSortedIteratorImpl(getIterator()); + } + private void reset_(Envelope2D extent, int height) { - // We need 2 * height bits for the morten number, which is of type - // Index_type (more than enough). - if (height < 0 || 2 * height > 8 * 4) + if (height < 0 || height > 127) throw new IllegalArgumentException("invalid height"); m_height = height; m_extent.setCoords(extent); m_root = m_quad_tree_nodes.newElement(); - setSubTreeElementCount_(m_root, 0); - setLocalElementCount_(m_root, 0); - setMortenNumber_(m_root, 0); - setHeight_(m_root, 0); + m_data_extent.setEmpty(); + m_root = -1; } - private int insert_(int element, Envelope2D bounding_box, int height, - Envelope2D quad_extent, int quad_handle, boolean b_flushing, - int flushed_element_handle) { + private int insert_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { if (!quad_extent.contains(bounding_box)) { assert (!b_flushing); if (height == 0) return -1; - return insert_(element, bounding_box, 0, m_extent, m_root, - b_flushing, flushed_element_handle); + return insert_(element, bounding_box, 0, m_extent, m_root, b_flushing, flushed_element_handle); } if (!b_flushing) { - for (int q = quad_handle; q != -1; q = getParent_(q)) - setSubTreeElementCount_(q, getSubTreeElementCount_(q) + 1); + for (int q = quad_handle; q != -1; q = get_parent_(q)) + set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) + 1); } Envelope2D current_extent = new Envelope2D(); @@ -505,9 +809,8 @@ private int insert_(int element, Envelope2D bounding_box, int height, child_extents[3] = new Envelope2D(); int current_height; - for (current_height = height; current_height < m_height - && canPushDown_(current_quad_handle); current_height++) { - setChildExtents_(current_extent, child_extents); + for (current_height = height; current_height < m_height && can_push_down_(current_quad_handle); current_height++) { + set_child_extents_(current_extent, child_extents); boolean b_contains = false; @@ -515,12 +818,11 @@ private int insert_(int element, Envelope2D bounding_box, int height, if (child_extents[i].contains(bounding_box)) { b_contains = true; - int child_handle = getChild_(current_quad_handle, i); + int child_handle = get_child_(current_quad_handle, i); if (child_handle == -1) - child_handle = createChild_(current_quad_handle, i); + child_handle = create_child_(current_quad_handle, i); - setSubTreeElementCount_(child_handle, - getSubTreeElementCount_(child_handle) + 1); + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); current_quad_handle = child_handle; current_extent.setCoords(child_extents[i]); @@ -532,22 +834,112 @@ private int insert_(int element, Envelope2D bounding_box, int height, break; } - return insertAtQuad_(element, bounding_box, current_height, - current_extent, current_quad_handle, b_flushing, quad_handle, - flushed_element_handle); + return insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, -1); } - private int insertAtQuad_(int element, Envelope2D bounding_box, - int current_height, Envelope2D current_extent, - int current_quad_handle, boolean b_flushing, int quad_handle, - int flushed_element_handle) { - // If the bounding box is not contained in any of the current_node's - // children, or if the current_height is m_height, then insert the - // element and + private int insert_duplicates_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { + assert (b_flushing || m_root == quad_handle); + + if (!b_flushing) // If b_flushing is true, then the sub tree element counts are already accounted for since the element already lies in the current incoming quad + { + if (!quad_extent.contains(bounding_box)) + return -1; + + set_sub_tree_element_count_(quad_handle, get_sub_tree_element_count_(quad_handle) + 1); + set_contained_sub_tree_element_count_(quad_handle, get_contained_sub_tree_element_count_(quad_handle) + 1); + } + + double bounding_box_max_dim = Math.max(bounding_box.getWidth(), bounding_box.getHeight()); + + int element_handle = -1; + AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); + ArrayList extents_stack = new ArrayList(0); + AttributeStreamOfInt32 heights_stack = new AttributeStreamOfInt32(0); + quads_stack.add(quad_handle); + extents_stack.add(new Envelope2D(quad_extent.xmin, quad_extent.ymin, quad_extent.xmax, quad_extent.ymax)); + heights_stack.add(height); + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + Envelope2D current_extent = new Envelope2D(); + + while (quads_stack.size() > 0) { + boolean b_subdivide = false; + + int current_quad_handle = quads_stack.getLast(); + current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); + int current_height = heights_stack.getLast(); + + quads_stack.removeLast(); + extents_stack.remove(extents_stack.size() - 1); + heights_stack.removeLast(); + + if (current_height + 1 < m_height && can_push_down_(current_quad_handle)) { + double current_extent_max_dim = Math.max(current_extent.getWidth(), current_extent.getHeight()); + + if (bounding_box_max_dim <= current_extent_max_dim / 2.0) + b_subdivide = true; + } + + if (b_subdivide) { + set_child_extents_(current_extent, child_extents); + + boolean b_contains = false; + + for (int i = 0; i < 4; i++) { + b_contains = child_extents[i].contains(bounding_box); + + if (b_contains) { + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + heights_stack.add(current_height + 1); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + set_contained_sub_tree_element_count_(child_handle, get_contained_sub_tree_element_count_(child_handle) + 1); + break; + } + } + + if (!b_contains) { + for (int i = 0; i < 4; i++) { + boolean b_intersects = child_extents[i].isIntersecting(bounding_box); + + if (b_intersects) { + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + heights_stack.add(current_height + 1); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + } + } + } + } else { + element_handle = insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, element_handle); + b_flushing = false; // flushing is false after the first inserted element has been flushed down, all subsequent inserts will be new + } + } + + return 0; + } + + private int insert_at_quad_(int element, Envelope2D bounding_box, int current_height, Envelope2D current_extent, int current_quad_handle, boolean b_flushing, int quad_handle, int flushed_element_handle, int duplicate_element_handle) { + // If the bounding box is not contained in any of the current_node's children, or if the current_height is m_height, then insert the element and // bounding box into the current_node - int head_element_handle = getFirstElement_(current_quad_handle); - int tail_element_handle = getLastElement_(current_quad_handle); + int head_element_handle = get_first_element_(current_quad_handle); + int tail_element_handle = get_last_element_(current_quad_handle); int element_handle = -1; if (b_flushing) { @@ -556,295 +948,350 @@ private int insertAtQuad_(int element, Envelope2D bounding_box, if (current_quad_handle == quad_handle) return flushed_element_handle; - disconnectElementHandle_(flushed_element_handle); // Take it out of - // the incoming - // quad_handle, - // and place in - // current_quad_handle + disconnect_element_handle_(flushed_element_handle); // Take it out of the incoming quad_handle, and place in current_quad_handle element_handle = flushed_element_handle; } else { - element_handle = createElementAndBoxNode_(); - setElement_(element_handle, element); // insert element at the new - // tail of the list - // (next_element_handle). - setBoundingBox_(getBoxHandle_(element_handle), bounding_box); // insert - // bounding_box + if (duplicate_element_handle == -1) { + element_handle = create_element_(); + set_data_values_(get_data_(element_handle), element, bounding_box); + } else { + assert (m_b_store_duplicates); + element_handle = create_element_from_duplicate_(duplicate_element_handle); + } } assert (!b_flushing || element_handle == flushed_element_handle); - setQuad_(element_handle, current_quad_handle); // set parent quad - // (needed for removal - // of element) + set_quad_(element_handle, current_quad_handle); // set parent quad (needed for removal of element) - // assign the prev pointer of the new tail to point at the old tail - // (tail_element_handle) - // assign the next pointer of the old tail to point at the new tail - // (next_element_handle) + // assign the prev pointer of the new tail to point at the old tail (tail_element_handle) + // assign the next pointer of the old tail to point at the new tail (next_element_handle) if (tail_element_handle != -1) { - setPrevElement_(element_handle, tail_element_handle); - setNextElement_(tail_element_handle, element_handle); + set_prev_element_(element_handle, tail_element_handle); + set_next_element_(tail_element_handle, element_handle); } else { assert (head_element_handle == -1); - setFirstElement_(current_quad_handle, element_handle); + set_first_element_(current_quad_handle, element_handle); } // assign the new tail - setLastElement_(current_quad_handle, element_handle); + set_last_element_(current_quad_handle, element_handle); - setLocalElementCount_(current_quad_handle, - getLocalElementCount_(current_quad_handle) + 1); + set_local_element_count_(current_quad_handle, get_local_element_count_(current_quad_handle) + 1); - if (canFlush_(current_quad_handle)) + if (can_flush_(current_quad_handle)) flush_(current_height, current_extent, current_quad_handle); return element_handle; } - private int disconnectElementHandle_(int element_handle) { + private static void set_child_extents_(Envelope2D current_extent, Envelope2D[] child_extents) { + double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); + double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + + child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, current_extent.ymax); // northeast + child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, current_extent.ymax); // northwest + child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, x_mid, y_mid); // southwest + child_extents[3].setCoords(x_mid, current_extent.ymin, current_extent.xmax, y_mid); // southeast + } + + private void disconnect_element_handle_(int element_handle) { assert (element_handle != -1); - int quad_handle = getQuad_(element_handle); - int head_element_handle = getFirstElement_(quad_handle); - int tail_element_handle = getLastElement_(quad_handle); - int prev_element_handle = getPrevElement_(element_handle); - int next_element_handle = getNextElement_(element_handle); + int quad_handle = get_quad_(element_handle); + int head_element_handle = get_first_element_(quad_handle); + int tail_element_handle = get_last_element_(quad_handle); + int prev_element_handle = get_prev_element_(element_handle); + int next_element_handle = get_next_element_(element_handle); assert (head_element_handle != -1 && tail_element_handle != -1); if (head_element_handle == element_handle) { if (next_element_handle != -1) - setPrevElement_(next_element_handle, -1); + set_prev_element_(next_element_handle, -1); else { assert (head_element_handle == tail_element_handle); - assert (getLocalElementCount_(quad_handle) == 1); - setLastElement_(quad_handle, -1); + assert (get_local_element_count_(quad_handle) == 1); + set_last_element_(quad_handle, -1); } - setFirstElement_(quad_handle, next_element_handle); + set_first_element_(quad_handle, next_element_handle); } else if (tail_element_handle == element_handle) { assert (prev_element_handle != -1); - assert (getLocalElementCount_(quad_handle) >= 2); - setNextElement_(prev_element_handle, -1); - setLastElement_(quad_handle, prev_element_handle); + assert (get_local_element_count_(quad_handle) >= 2); + set_next_element_(prev_element_handle, -1); + set_last_element_(quad_handle, prev_element_handle); } else { assert (next_element_handle != -1 && prev_element_handle != -1); - assert (getLocalElementCount_(quad_handle) >= 3); - setPrevElement_(next_element_handle, prev_element_handle); - setNextElement_(prev_element_handle, next_element_handle); + assert (get_local_element_count_(quad_handle) >= 3); + set_prev_element_(next_element_handle, prev_element_handle); + set_next_element_(prev_element_handle, next_element_handle); } - setPrevElement_(element_handle, -1); - setNextElement_(element_handle, -1); - - setLocalElementCount_(quad_handle, - getLocalElementCount_(quad_handle) - 1); - assert (getLocalElementCount_(quad_handle) >= 0); + set_prev_element_(element_handle, -1); + set_next_element_(element_handle, -1); - return next_element_handle; + set_local_element_count_(quad_handle, get_local_element_count_(quad_handle) - 1); + assert (get_local_element_count_(quad_handle) >= 0); } - private static void setChildExtents_(Envelope2D current_extent, - Envelope2D[] child_extents) { - double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); - double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); - - child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, - current_extent.ymax); // northeast - child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, - current_extent.ymax); // northwest - child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, - x_mid, y_mid); // southwest - child_extents[3].setCoords(x_mid, current_extent.ymin, - current_extent.xmax, y_mid); // southeast - } - - private boolean canFlush_(int quad_handle) { - return getLocalElementCount_(quad_handle) == 8 - && !hasChildren_(quad_handle); + private boolean can_flush_(int quad_handle) { + return get_local_element_count_(quad_handle) == m_flushing_count && !has_children_(quad_handle); } private void flush_(int height, Envelope2D extent, int quad_handle) { int element; - Envelope2D bounding_box; + Envelope2D bounding_box = new Envelope2D(); assert (quad_handle != -1); - int element_handle = getFirstElement_(quad_handle), next_handle; - int box_handle; + int element_handle = get_first_element_(quad_handle), next_handle = -1; + int data_handle = -1; assert (element_handle != -1); do { - box_handle = getBoxHandle_(element_handle); - element = m_element_nodes.getField(element_handle, 0); - bounding_box = getBoundingBox_(box_handle); - insert_(element, bounding_box, height, extent, quad_handle, true, - element_handle); + data_handle = get_data_(element_handle); + element = get_element_value_(data_handle); + bounding_box.setCoords(get_bounding_box_value_(data_handle)); + + next_handle = get_next_element_(element_handle); + + if (!m_b_store_duplicates) + insert_(element, bounding_box, height, extent, quad_handle, true, element_handle); + else + insert_duplicates_(element, bounding_box, height, extent, quad_handle, true, element_handle); - next_handle = getNextElement_(element_handle); element_handle = next_handle; } while (element_handle != -1); } - boolean canPushDown_(int quad_handle) { - return getLocalElementCount_(quad_handle) >= 8 - || hasChildren_(quad_handle); + private boolean can_push_down_(int quad_handle) { + return get_local_element_count_(quad_handle) >= m_flushing_count || has_children_(quad_handle); } - boolean hasChildren_(int parent) { - return getChild_(parent, 0) != -1 || getChild_(parent, 1) != -1 - || getChild_(parent, 2) != -1 || getChild_(parent, 3) != -1; + private boolean has_children_(int parent) { + return get_child_(parent, 0) != -1 || get_child_(parent, 1) != -1 || get_child_(parent, 2) != -1 || get_child_(parent, 3) != -1; } - private int createChild_(int parent, int quadrant) { + private int create_child_(int parent, int quadrant) { int child = m_quad_tree_nodes.newElement(); - setChild_(parent, quadrant, child); - setSubTreeElementCount_(child, 0); - setLocalElementCount_(child, 0); - setParent_(child, parent); - setHeight_(child, getHeight_(parent) + 1); - setMortenNumber_(child, (quadrant << (2 * getHeight_(parent))) - | getMortenNumber_(parent)); + set_child_(parent, quadrant, child); + set_sub_tree_element_count_(child, 0); + set_local_element_count_(child, 0); + set_parent_(child, parent); + set_height_and_quadrant_(child, get_height_(parent) + 1, quadrant); + + if (m_b_store_duplicates) + set_contained_sub_tree_element_count_(child, 0); + return child; } - private int createElementAndBoxNode_() { + private void create_root_() { + m_root = m_quad_tree_nodes.newElement(); + set_sub_tree_element_count_(m_root, 0); + set_local_element_count_(m_root, 0); + set_height_and_quadrant_(m_root, 0, 0); + + if (m_b_store_duplicates) + set_contained_sub_tree_element_count_(m_root, 0); + } + + private int create_element_() { int element_handle = m_element_nodes.newElement(); - int box_handle; + int data_handle; - if (m_free_boxes.size() > 0) { - box_handle = m_free_boxes.getLast(); - m_free_boxes.removeLast(); + if (m_free_data.size() > 0) { + data_handle = m_free_data.get(m_free_data.size() - 1); + m_free_data.removeLast(); } else { - box_handle = m_boxes.size(); - m_boxes.add(new Envelope2D()); + data_handle = m_data.size(); + m_data.add(null); } - setBoxHandle_(element_handle, box_handle); + set_data_(element_handle, data_handle); return element_handle; } - private void freeElementAndBoxNode_(int element_handle) { - m_free_boxes.add(getBoxHandle_(element_handle)); + private int create_element_from_duplicate_(int duplicate_element_handle) { + int element_handle = m_element_nodes.newElement(); + int data_handle = get_data_(duplicate_element_handle); + set_data_(element_handle, data_handle); + return element_handle; + } + + private void free_element_and_box_node_(int element_handle) { + int data_handle = get_data_(element_handle); + m_free_data.add(data_handle); m_element_nodes.deleteElement(element_handle); } - private int getChild_(int quad_handle, int quadrant) { + private int get_child_(int quad_handle, int quadrant) { return m_quad_tree_nodes.getField(quad_handle, quadrant); } - private void setChild_(int parent, int quadrant, int child) { + private void set_child_(int parent, int quadrant, int child) { m_quad_tree_nodes.setField(parent, quadrant, child); } - private int getFirstElement_(int quad_handle) { + private int get_first_element_(int quad_handle) { return m_quad_tree_nodes.getField(quad_handle, 4); } - private void setFirstElement_(int quad_handle, int head) { + private void set_first_element_(int quad_handle, int head) { m_quad_tree_nodes.setField(quad_handle, 4, head); } - private int getLastElement_(int quad_handle) { + private int get_last_element_(int quad_handle) { return m_quad_tree_nodes.getField(quad_handle, 5); } - private void setLastElement_(int quad_handle, int tail) { + private void set_last_element_(int quad_handle, int tail) { m_quad_tree_nodes.setField(quad_handle, 5, tail); } - private int getMortenNumber_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 6); + + private int get_quadrant_(int quad_handle) { + int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); + int quadrant = height_quadrant_hybrid & m_quadrant_mask; + return quadrant; } - private void setMortenNumber_(int quad_handle, int morten_number) { - m_quad_tree_nodes.setField(quad_handle, 6, morten_number); + private int get_height_(int quad_handle) { + int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); + int height = height_quadrant_hybrid >> m_height_bit_shift; + return height; } - private int getLocalElementCount_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 7); + private void set_height_and_quadrant_(int quad_handle, int height, int quadrant) { + assert (quadrant >= 0 && quadrant <= 3); + int height_quadrant_hybrid = (int) ((height << m_height_bit_shift) | quadrant); + m_quad_tree_nodes.setField(quad_handle, 6, height_quadrant_hybrid); } - private int getSubTreeElementCount_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 8); + private int get_local_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 7); } - private void setLocalElementCount_(int quad_handle, int count) { + private void set_local_element_count_(int quad_handle, int count) { m_quad_tree_nodes.setField(quad_handle, 7, count); } - private void setSubTreeElementCount_(int quad_handle, int count) { + private int get_sub_tree_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 8); + } + + private void set_sub_tree_element_count_(int quad_handle, int count) { m_quad_tree_nodes.setField(quad_handle, 8, count); } - private int getParent_(int child) { + private int get_parent_(int child) { return m_quad_tree_nodes.getField(child, 9); } - private void setParent_(int child, int parent) { + private void set_parent_(int child, int parent) { m_quad_tree_nodes.setField(child, 9, parent); } - private int getHeight_(int quad_handle) { - return (int) m_quad_tree_nodes.getField(quad_handle, 10); + private int get_contained_sub_tree_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 10); } - private void setHeight_(int quad_handle, int height) { - m_quad_tree_nodes.setField(quad_handle, 10, height); + private void set_contained_sub_tree_element_count_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 10, count); } - private int getElement_(int element_handle) { + private int get_data_(int element_handle) { return m_element_nodes.getField(element_handle, 0); } - private void setElement_(int element_handle, int element) { - m_element_nodes.setField(element_handle, 0, element); + private void set_data_(int element_handle, int data_handle) { + m_element_nodes.setField(element_handle, 0, data_handle); } - private int getPrevElement_(int element_handle) { + private int get_prev_element_(int element_handle) { return m_element_nodes.getField(element_handle, 1); } - private int getNextElement_(int element_handle) { + private int get_next_element_(int element_handle) { return m_element_nodes.getField(element_handle, 2); } - private void setPrevElement_(int element_handle, int prev_handle) { + private void set_prev_element_(int element_handle, int prev_handle) { m_element_nodes.setField(element_handle, 1, prev_handle); } - private void setNextElement_(int element_handle, int next_handle) { + private void set_next_element_(int element_handle, int next_handle) { m_element_nodes.setField(element_handle, 2, next_handle); } - private int getQuad_(int element_handle) { + private int get_quad_(int element_handle) { return m_element_nodes.getField(element_handle, 3); } - private void setQuad_(int element_handle, int parent) { + private void set_quad_(int element_handle, int parent) { m_element_nodes.setField(element_handle, 3, parent); } - private int getBoxHandle_(int element_handle) { - return m_element_nodes.getField(element_handle, 4); - } - - private void setBoxHandle_(int element_handle, int box_handle) { - m_element_nodes.setField(element_handle, 4, box_handle); + private int get_element_value_(int data_handle) { + return m_data.get(data_handle).element; } - private Envelope2D getBoundingBox_(int box_handle) { - return m_boxes.get(box_handle); + private Envelope2D get_bounding_box_value_(int data_handle) { + return m_data.get(data_handle).box; } - private void setBoundingBox_(int box_handle, Envelope2D bounding_box) { - m_boxes.get(box_handle).setCoords(bounding_box); + private void set_data_values_(int data_handle, int element, Envelope2D bounding_box) { + m_data.set(data_handle, new Data(element, bounding_box)); } - private int m_root; private Envelope2D m_extent; - private int m_height; + private Envelope2D m_data_extent; private StridedIndexTypeCollection m_quad_tree_nodes; private StridedIndexTypeCollection m_element_nodes; - private ArrayList m_boxes; - private AttributeStreamOfInt32 m_free_boxes; + private ArrayList m_data; + private AttributeStreamOfInt32 m_free_data; + private int m_root; + private int m_height; + private boolean m_b_store_duplicates; + + private int m_quadrant_mask = 3; + private int m_height_bit_shift = 2; + private int m_flushing_count = 5; + + static final class Data { + int element; + Envelope2D box; + + Data(int element_, Envelope2D box_) { + element = element_; + box = new Envelope2D(); + box.setCoords(box_); + } + } + + /* m_quad_tree_nodes + * 0: m_north_east_child + * 1: m_north_west_child + * 2: m_south_west_child + * 3: m_south_east_child + * 4: m_head_element + * 5: m_tail_element + * 6: m_quadrant_and_height + * 7: m_local_element_count + * 8: m_sub_tree_element_count + * 9: m_parent_quad + * 10: m_height + */ + + /* m_element_nodes + * 0: m_data_handle + * 1: m_prev + * 2: m_next + * 3: m_parent_quad + */ + + /* m_data + * element + * box + */ } diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index ded79caf..ff21c8ec 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -138,14 +138,16 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); double strokeHalfWidth = m_transform.transform(tol) + 1.5; - double shortSegment = 0.5; + double shortSegment = 0.25; Point2D vec = new Point2D(); Point2D vecA = new Point2D(); Point2D vecB = new Point2D(); - // TODO check this Java workaroung Point2D ptStart = new Point2D(); Point2D ptEnd = new Point2D(); + Point2D prev_start = new Point2D(); + Point2D prev_end = new Point2D(); + double[] helper_xy_10_elm = new double[10]; Envelope2D segEnv = new Envelope2D(); Point2D ptOld = new Point2D(); while (segIter.nextPath()) { @@ -155,18 +157,22 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, while (segIter.hasNextSegment()) { Segment seg = segIter.nextSegment(); ptStart.x = seg.getStartX(); - ptStart.y = seg.getStartY();// Point2D ptStart = - // seg.getStartXY(); + ptStart.y = seg.getStartY(); ptEnd.x = seg.getEndX(); - ptEnd.y = seg.getEndY();// Point2D ptEnd = seg.getEndXY(); + ptEnd.y = seg.getEndY(); segEnv.setEmpty(); segEnv.merge(ptStart.x, ptStart.y); segEnv.mergeNE(ptEnd.x, ptEnd.y); if (!m_geomEnv.isIntersectingNE(segEnv)) { if (hasFan) { - fillConvexPolygon(rasterizer, fan, 4); + rasterizer.startAddingEdges(); + rasterizer.addSegmentStroke(prev_start.x, prev_start.y, + prev_end.x, prev_end.y, strokeHalfWidth, false, + helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); hasFan = false; } + first = true; continue; } @@ -181,34 +187,25 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, ptStart.setCoords(ptOld); } - vec.sub(ptEnd, ptStart); - double len = vec.length(); - boolean bShort = len < shortSegment; - if (len == 0) { - vec.setCoords(1.0, 0); - len = 1.0; - continue; - } + prev_start.setCoords(ptStart); + prev_end.setCoords(ptEnd); + + rasterizer.startAddingEdges(); + hasFan = !rasterizer.addSegmentStroke(prev_start.x, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + true, helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + if (!hasFan) + ptOld.setCoords(prev_end); + } - if (!bShort) - ptOld.setCoords(ptEnd); - - vec.scale(strokeHalfWidth / len); - vecA.setCoords(-vec.y, vec.x); - vecB.setCoords(vec.y, -vec.x); - ptStart.sub(vec); - ptEnd.add(vec); - fan[0].add(ptStart, vecA); - fan[1].add(ptStart, vecB); - fan[2].add(ptEnd, vecB); - fan[3].add(ptEnd, vecA); - if (!bShort) - fillConvexPolygon(rasterizer, fan, 4); - else - hasFan = true; + if (hasFan) { + rasterizer.startAddingEdges(); + hasFan = !rasterizer.addSegmentStroke(prev_start.x, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + false, helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); } - if (hasFan) - fillConvexPolygon(rasterizer, fan, 4); } } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index be94b723..ca2ea184 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -25,16 +25,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; /** * A base class for segments. Presently only Line segments are supported. */ public abstract class Segment extends Geometry implements Serializable { - - // UPDATED PORT TO MATCH NATIVE AS OF JAN 30 2011 - private static final long serialVersionUID = 1L; - double m_xStart; double m_yStart; @@ -49,11 +46,11 @@ public abstract class Segment extends Geometry implements Serializable { /** * Returns XY coordinates of the start point. */ - Point2D getStartXY() { + public Point2D getStartXY() { return Point2D.construct(m_xStart, m_yStart); } - void getStartXY(Point2D pt) { + public void getStartXY(Point2D pt) { pt.x = m_xStart; pt.y = m_yStart; } @@ -61,29 +58,29 @@ void getStartXY(Point2D pt) { /** * Sets the XY coordinates of the start point. */ - void setStartXY(Point2D pt) { + public void setStartXY(Point2D pt) { _setXY(0, pt); } - void setStartXY(double x, double y) { + public void setStartXY(double x, double y) { _setXY(0, Point2D.construct(x, y)); } /** * Returns XYZ coordinates of the start point. Z if 0 if Z is missing. */ - Point3D getStartXYZ() { + public Point3D getStartXYZ() { return _getXYZ(0); } /** * Sets the XYZ coordinates of the start point. */ - void setStartXYZ(Point3D pt) { + public void setStartXYZ(Point3D pt) { _setXYZ(0, pt); } - void setStartXYZ(double x, double y, double z) { + public void setStartXYZ(double x, double y, double z) { _setXYZ(0, Point3D.construct(x, y, z)); } @@ -193,11 +190,11 @@ public double getEndY() { * * @return The XY coordinates of the end point. */ - Point2D getEndXY() { + public Point2D getEndXY() { return Point2D.construct(m_xEnd, m_yEnd); } - void getEndXY(Point2D pt) { + public void getEndXY(Point2D pt) { pt.x = m_xEnd; pt.y = m_yEnd; } @@ -208,11 +205,11 @@ void getEndXY(Point2D pt) { * @param pt * The end point of the segment. */ - void setEndXY(Point2D pt) { + public void setEndXY(Point2D pt) { _setXY(1, pt); } - void setEndXY(double x, double y) { + public void setEndXY(double x, double y) { _setXY(1, Point2D.construct(x, y)); } @@ -221,18 +218,18 @@ void setEndXY(double x, double y) { * * @return The XYZ coordinates of the end point. */ - Point3D getEndXYZ() { + public Point3D getEndXYZ() { return _getXYZ(1); } /** * Sets the XYZ coordinates of the end point. */ - void setEndXYZ(Point3D pt) { + public void setEndXYZ(Point3D pt) { _setXYZ(1, pt); } - void setEndXYZ(double x, double y, double z) { + public void setEndXYZ(double x, double y, double z) { _setXYZ(1, Point3D.construct(x, y, z)); } @@ -358,7 +355,7 @@ int intersect(Segment other, Point2D[] intersectionPoints, * Returns TRUE if this segment intersects with the other segment with the * given tolerance. */ - boolean isIntersecting(Segment other, double tolerance) { + public boolean isIntersecting(Segment other, double tolerance) { return _isIntersecting(other, tolerance, false) != 0; } @@ -366,7 +363,7 @@ boolean isIntersecting(Segment other, double tolerance) { * Returns TRUE if the point and segment intersect (not disjoint) for the * given tolerance. */ - boolean isIntersecting(Point2D pt, double tolerance) { + public boolean isIntersecting(Point2D pt, double tolerance) { return _isIntersectingPoint(pt, tolerance, false); } @@ -485,7 +482,7 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; int old_offset0 = _getEndPointOffset(m_description, 0); int old_offset1 = _getEndPointOffset(m_description, 1); @@ -583,7 +580,7 @@ private void _set(int endPoint, Point src) { int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) { if (m_attributes != null) - _resizeAttributes(m_description._getTotalComponents() - 2); + _resizeAttributes(m_description.getTotalComponentCount() - 2); return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) @@ -626,7 +623,7 @@ else if (ordinate != 0) } if (m_attributes == null) - _resizeAttributes(m_description._getTotalComponents() - 2); + _resizeAttributes(m_description.getTotalComponentCount() - 2); m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 @@ -645,9 +642,9 @@ public void copyTo(Geometry dst) { Segment segDst = (Segment) dst; segDst.m_description = m_description; - segDst._resizeAttributes(m_description._getTotalComponents() - 2); + segDst._resizeAttributes(m_description.getTotalComponentCount() - 2); _attributeCopy(m_attributes, 0, segDst.m_attributes, 0, - (m_description._getTotalComponents() - 2) * 2); + (m_description.getTotalComponentCount() - 2) * 2); segDst.m_xStart = m_xStart; segDst.m_yStart = m_yStart; segDst.m_xEnd = m_xEnd; @@ -691,7 +688,7 @@ boolean _equalsImpl(Segment other) { if (m_xStart != other.m_xStart || m_xEnd != other.m_xEnd || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) return false; - for (int i = 0; i < (m_description._getTotalComponents() - 2) * 2; i++) + for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) if (m_attributes[i] != other.m_attributes[i]) return false; @@ -711,10 +708,6 @@ boolean isClosed() { void reverse() { _reverseImpl(); - // because java doesn't support passing value types - // by reference numberutils swap won't work - // NumberUtils.swap(m_xStart, m_xEnd); - // NumberUtils.swap(m_yStart, m_yEnd); double origxStart = m_xStart; double origxEnd = m_xEnd; m_xStart = origxEnd; @@ -778,14 +771,14 @@ int _intersect(Segment other, Point2D[] intersectionPoints, abstract double _calculateArea2DHelper(double xorg, double yorg); static int _getEndPointOffset(VertexDescription vd, int endPoint) { - return endPoint * (vd._getTotalComponents() - 2); + return endPoint * (vd.getTotalComponentCount() - 2); } /** * Returns the coordinate of the point on this segment for the given * parameter value. */ - Point2D getCoord2D(double t) { + public Point2D getCoord2D(double t) { Point2D pt = new Point2D(); getCoord2D(t, pt); return pt; @@ -801,7 +794,7 @@ Point2D getCoord2D(double t) { * @param dst * the coordinate where result will be placed. */ - abstract void getCoord2D(double t, Point2D dst); + public abstract void getCoord2D(double t, Point2D dst); /** * Finds a closest coordinate on this segment. @@ -817,7 +810,7 @@ Point2D getCoord2D(double t) { * obtain the 2D coordinate on the segment from t. To find the * distance, call (inputPoint.sub(seg.getCoord2D(t))).length(); */ - abstract double getClosestCoordinate(Point2D inputPoint, + public abstract double getClosestCoordinate(Point2D inputPoint, boolean bExtrapolate); /** @@ -887,7 +880,7 @@ void _reverseImpl() { * Returns subsegment between parameters t1 and t2. The attributes are * interpolated along the length of the curve. */ - abstract Segment cut(double t1, double t2); + public abstract Segment cut(double t1, double t2); /** * Calculates the subsegment between parameters t1 and t2, and stores the @@ -927,8 +920,8 @@ abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, abstract double lengthToT(double len); - double distance(/* const */Segment otherSegment, - boolean bSegmentsKnownDisjoint) /* const */ + public double distance(/* const */Segment otherSegment, + boolean bSegmentsKnownDisjoint) { // if the segments are not known to be disjoint, and // the segments are found to touch in any way, then return 0.0 diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index 8312e028..089f93d8 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -25,7 +25,17 @@ package com.esri.core.geometry; /** - * This class provides functionality to iterate over multipath segments. + * This class provides functionality to iterate over MultiPath segments. + * + * Example: + *


+ * SegmentIterator iterator = polygon.querySegmentIterator();
+ * while (iterator.nextPath()) {
+ *   while (iterator.hasNextSegment()) {
+ *     Segment segment = iterator.nextSegment();
+ *   }
+ * }
+ * 
*/ public class SegmentIterator { private SegmentIteratorImpl m_impl; diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 783b8a8f..de817443 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -295,6 +295,50 @@ public final void fillEnvelope(Envelope2D envIn) { } } + final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, double[] helper_xy_10_elm) + { + double vec_x = x2 - x1; + double vec_y = y2 - y1; + double len = Math.sqrt(vec_x * vec_x + vec_y * vec_y); + if (skip_short && len < 0.5) + return false; + + boolean bshort = len < 0.00001; + if (bshort) + { + len = 0.00001; + vec_x = len; + vec_y = 0.0; + } + + double f = half_width / len; + vec_x *= f; vec_y *= f; + double vecA_x = -vec_y; + double vecA_y = vec_x; + double vecB_x = vec_y; + double vecB_y = -vec_x; + //extend by half width + x1 -= vec_x; + y1 -= vec_y; + x2 += vec_x; + y2 += vec_y; + //create rotated rectangle + double[] fan = helper_xy_10_elm; + assert(fan.length == 10); + fan[0] = x1 + vecA_x; + fan[1] = y1 + vecA_y;//fan[0].add(pt_start, vecA); + fan[2] = x1 + vecB_x; + fan[3] = y1 + vecB_y;//fan[1].add(pt_start, vecB); + fan[4] = x2 + vecB_x; + fan[5] = y2 + vecB_y;//fan[2].add(pt_end, vecB) + fan[6] = x2 + vecA_x; + fan[7] = y2 + vecA_y;//fan[3].add(pt_end, vecA) + fan[8] = fan[0]; + fan[9] = fan[1]; + addRing(fan); + return true; + } + public final ScanCallback getScanCallback() { return callback_; } diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 77520c40..1aa5886a 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -230,10 +231,77 @@ static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 double rpu = Math.PI / 180.0; PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getXY().x * rpu, - ptFrom.getXY().y * rpu, ptTo.getXY().x * rpu, ptTo.getXY().y + GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getX() * rpu, + ptFrom.getY() * rpu, ptTo.getX() * rpu, ptTo.getY() * rpu, answer, null, null); return answer.val; } + public String getAuthority() { + int latestWKID = getLatestID(); + if (latestWKID <= 0) + return new String(""); + + return getAuthority_(latestWKID); + } + + private String getAuthority_(int latestWKID) { + String authority; + + if (latestWKID >= 1024 && latestWKID <= 32767) { + + int index = Arrays.binarySearch(m_esri_codes, latestWKID); + + if (index >= 0) + authority = new String("ESRI"); + else + authority = new String("EPSG"); + } else { + authority = new String("ESRI"); + } + + return authority; + } + + private static final int[] m_esri_codes = { + 2181, // ED_1950_Turkey_9 + 2182, // ED_1950_Turkey_10 + 2183, // ED_1950_Turkey_11 + 2184, // ED_1950_Turkey_12 + 2185, // ED_1950_Turkey_13 + 2186, // ED_1950_Turkey_14 + 2187, // ED_1950_Turkey_15 + 4305, // GCS_Voirol_Unifie_1960 + 4812, // GCS_Voirol_Unifie_1960_Paris + 20002, // Pulkovo_1995_GK_Zone_2 + 20003, // Pulkovo_1995_GK_Zone_3 + 20062, // Pulkovo_1995_GK_Zone_2N + 20063, // Pulkovo_1995_GK_Zone_3N + 24721, // La_Canoa_UTM_Zone_21N + 26761, // NAD_1927_StatePlane_Hawaii_1_FIPS_5101 + 26762, // NAD_1927_StatePlane_Hawaii_2_FIPS_5102 + 26763, // NAD_1927_StatePlane_Hawaii_3_FIPS_5103 + 26764, // NAD_1927_StatePlane_Hawaii_4_FIPS_5104 + 26765, // NAD_1927_StatePlane_Hawaii_5_FIPS_5105 + 26788, // NAD_1927_StatePlane_Michigan_North_FIPS_2111 + 26789, // NAD_1927_StatePlane_Michigan_Central_FIPS_2112 + 26790, // NAD_1927_StatePlane_Michigan_South_FIPS_2113 + 30591, // Nord_Algerie + 30592, // Sud_Algerie + 31491, // Germany_Zone_1 + 31492, // Germany_Zone_2 + 31493, // Germany_Zone_3 + 31494, // Germany_Zone_4 + 31495, // Germany_Zone_5 + 32059, // NAD_1927_StatePlane_Puerto_Rico_FIPS_5201 + 32060, // NAD_1927_StatePlane_Virgin_Islands_St_Croix_FIPS_5202 + }; + + @Override + public int hashCode() { + if (m_userWkid != 0) + return NumberUtils.hash(m_userWkid); + + return m_userWkt.hashCode(); + } } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index d1472399..99ea7cde 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -25,8 +25,10 @@ package com.esri.core.geometry; /** - * The affine transformation class for 2D.
- * Vector is a row: + * The affine transformation class for 2D. + * + * Vector is a row: + * *
|m11 m12 0| *
| x y 1| * |m21 m22 0| = |m11 * x + m21 * y + m31 m12 * x + m22 * y + m32 1| *
|m31 m32 1| @@ -456,9 +458,9 @@ public boolean isIdentity() { * The tolerance value. */ public boolean isIdentity(double tol) { - Point2D pt = Point2D.construct(0.0, 1.0); + Point2D pt = Point2D.construct(0., 1.); transform(pt, pt); - pt.sub(Point2D.construct(0.0, 1.0)); + pt.sub(Point2D.construct(0., 1.)); if (pt.sqrLength() > tol * tol) return false; @@ -469,7 +471,7 @@ public boolean isIdentity(double tol) { pt.setCoords(1.0, 0.0); transform(pt, pt); - pt.sub(Point2D.construct(1.0, 0)); + pt.sub(Point2D.construct(1.0, 0.0)); return pt.sqrLength() <= tol * tol; } diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 02719a3c..99702706 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -25,18 +25,13 @@ package com.esri.core.geometry; /** - * @brief The 3D affine transformation Vector is a row: |m11 m12 0| | x y 1| * - * |m21 m22 0| = |m11 * x + m21 * y + m31 m12 * x + m22 * y + m32 1| |m31 - * m32 1| Then elements of the Transformation2D are as follows: |xx yx 0| - * | x y 1| * |xy yy 0| = |xx * x + xy * y + xd yx * x + yy * y + yd 1| - * |xd yd 1| + * The 3D affine transformation class. * - * We use matrices for transformations of the vectors as rows (case 2). - * That means the math expressions on the Geometry matrix operations - * should be writen like this: v' = v * M1 * M2 * M3 = ( (v * M1) * M2 ) - * * M3, where v is a vector, Mn are the matrices. This is equivalent to - * the following line of code: ResultVector = - * (M1.Mul(M2).Mul(M3)).Transform(Vector) + * We use matrices for transformations of the vectors as rows. That means the + * math expressions on the Geometry matrix operations should be writen like + * this: v' = v * M1 * M2 * M3 = ( (v * M1) * M2 ) * M3, where v is a vector, Mn + * are the matrices. This is equivalent to the following line of code: + * ResultVector = (M1.Mul(M2).Mul(M3)).Transform(Vector) */ final class Transformation3D { @@ -201,13 +196,11 @@ public static void multiply(Transformation3D a, Transformation3D b, * * @param src * The input transformation. - * @param dst - * The inverse of the input transformation. - * @throws Throws - * the GeometryException("math_singularity") exception if the - * Inverse can not be calculated. + * @param result + * The inverse of the input transformation. Throws the + * GeometryException("math singularity") exception if the Inverse + * can not be calculated. */ - // static public static void inverse(Transformation3D src, Transformation3D result) { double det = src.xx * (src.yy * src.zz - src.zy * src.yz) - src.yx * (src.xy * src.zz - src.zy * src.xz) + src.zx diff --git a/src/main/java/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java index 1fc1a6f6..07e7630b 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescription.java +++ b/src/main/java/com/esri/core/geometry/VertexDescription.java @@ -25,6 +25,8 @@ package com.esri.core.geometry; +import java.util.Arrays; + /** * Describes the vertex format of a Geometry. * @@ -40,67 +42,39 @@ * table. You may look the vertices of a Geometry as if they are stored in a * database table, and the VertexDescription defines the fields of the table. */ -public class VertexDescription { - - private double[] m_defaultPointAttributes; - private int[] m_pointAttributeOffsets; - int m_attributeCount; - int m_total_component_count; - - int[] m_semantics; - - int[] m_semanticsToIndexMap; - - int m_hash; +public final class VertexDescription { + /** + * Describes the attribute and, in case of predefined attributes, provides a + * hint of the attribute use. + */ + public interface Semantics { + static final int POSITION = 0; // xy coordinates of a point (2D + // vector of double, linear + // interpolation) - static double[] _defaultValues = { 0, 0, NumberUtils.NaN(), 0, 0, 0, 0, 0, - 0 }; + static final int Z = 1; // z coordinates of a point (double, + // linear interpolation) - static int[] _interpolation = { Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.LINEAR, Interpolation.NONE, Interpolation.ANGULAR, - Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.NONE, - }; + static final int M = 2; // m attribute (double, linear + // interpolation) - static int[] _persistence = { Persistence.enumDouble, - Persistence.enumDouble, Persistence.enumDouble, - Persistence.enumInt32, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumInt32, - }; + static final int ID = 3; // id (int, no interpolation) - static int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; + static final int NORMAL = 4; // xyz coordinates of normal vector + // (float, angular interpolation) - static int[] _components = { 2, 1, 1, 1, 3, 1, 2, 3, 2, }; + static final int TEXTURE1D = 5; // u coordinates of texture + // (float, linear interpolation) - VertexDescription() { - m_attributeCount = 0; - m_total_component_count = 0; + static final int TEXTURE2D = 6; // uv coordinates of texture + // (float, linear interpolation) - } + static final int TEXTURE3D = 7; // uvw coordinates of texture + // (float, linear interpolation) - VertexDescription(int hashValue, VertexDescription other) { - m_attributeCount = other.m_attributeCount; - m_total_component_count = other.m_total_component_count; - m_semantics = other.m_semantics.clone(); - m_semanticsToIndexMap = other.m_semanticsToIndexMap.clone(); - m_hash = other.m_hash; + static final int ID2 = 8; // two component ID - // Prepare default values for the Point geometry. - m_pointAttributeOffsets = new int[getAttributeCount()]; - int offset = 0; - for (int i = 0; i < getAttributeCount(); i++) { - m_pointAttributeOffsets[i] = offset; - offset += getComponentCount(m_semantics[i]); - } - m_total_component_count = offset; - m_defaultPointAttributes = new double[offset]; - for (int i = 0; i < getAttributeCount(); i++) { - int components = getComponentCount(getSemantics(i)); - double dv = getDefaultValue(getSemantics(i)); - for (int icomp = 0; icomp < components; icomp++) - m_defaultPointAttributes[m_pointAttributeOffsets[i] + icomp] = dv; - } + static final int MAXSEMANTICS = 8; // the max semantics value } /** @@ -134,40 +108,6 @@ interface Persistence { public static final int enumInt16 = 5; }; - /** - * Describes the attribute and, in case of predefined attributes, provides a - * hint of the attribute use. - */ - public interface Semantics { - static final int POSITION = 0; // xy coordinates of a point (2D - // vector of double, linear - // interpolation) - - static final int Z = 1; // z coordinates of a point (double, - // linear interpolation) - - static final int M = 2; // m attribute (double, linear - // interpolation) - - static final int ID = 3; // id (int, no interpolation) - - static final int NORMAL = 4; // xyz coordinates of normal vector - // (float, angular interpolation) - - static final int TEXTURE1D = 5; // u coordinates of texture - // (float, linear interpolation) - - static final int TEXTURE2D = 6; // uv coordinates of texture - // (float, linear interpolation) - - static final int TEXTURE3D = 7; // uvw coordinates of texture - // (float, linear interpolation) - - static final int ID2 = 8; // two component ID - - static final int MAXSEMANTICS = 10; // the max semantics value - } - /** * Returns the attribute count of this description. The value is always * greater or equal to 1. The first attribute is always a POSITION. @@ -181,13 +121,10 @@ public final int getAttributeCount() { * * @param attributeIndex * The index of the attribute in the description. Max value is - * GetAttributeCount() - 1. + * getAttributeCount() - 1. */ public final int getSemantics(int attributeIndex) { - if (attributeIndex < 0 || attributeIndex > m_attributeCount) - throw new IllegalArgumentException(); - - return m_semantics[attributeIndex]; + return m_indexToSemantics[attributeIndex]; } /** @@ -249,20 +186,6 @@ public static int getComponentCount(int semantics) { return _components[semantics]; } - /** - * Returns True for integer persistence type. - */ - static boolean isIntegerPersistence(int persistence) { - return persistence < Persistence.enumInt32; - } - - /** - * Returns True for integer semantics type. - */ - static boolean isIntegerSemantics(int semantics) { - return isIntegerPersistence(getPersistence(semantics)); - } - /** * Returns True if the attribute with the given name and given set exists. * @@ -270,27 +193,40 @@ static boolean isIntegerSemantics(int semantics) { * The semantics of the attribute. */ public boolean hasAttribute(int semantics) { - return m_semanticsToIndexMap[semantics] >= 0; + return (m_semanticsBitArray & (1 << semantics)) != 0; + } + + /** + * Returns True if this vertex description includes all attributes from the + * src. + * + * @param src + * The Vertex_description to compare with. + * @return The function returns false, only when this description does not + * have some of the attribute that src has. + */ + public final boolean hasAttributesFrom(VertexDescription src) { + return (m_semanticsBitArray & src.m_semanticsBitArray) == src.m_semanticsBitArray; } /** * Returns True, if the vertex has Z attribute. */ - public boolean hasZ() { + public final boolean hasZ() { return hasAttribute(Semantics.Z); } /** * Returns True, if the vertex has M attribute. */ - public boolean hasM() { + public final boolean hasM() { return hasAttribute(Semantics.M); } /** * Returns True, if the vertex has ID attribute. */ - public boolean hasID() { + public final boolean hasID() { return hasAttribute(Semantics.ID); } @@ -302,15 +238,15 @@ public static double getDefaultValue(int semantics) { return _defaultValues[semantics]; } - int getPointAttributeOffset_(int attribute_index) { - return m_pointAttributeOffsets[attribute_index]; + int getPointAttributeOffset_(int attributeIndex) { + return m_pointAttributeOffsets[attributeIndex]; } /** * Returns the total component count. */ public int getTotalComponentCount() { - return m_total_component_count; + return m_totalComponentCount; } /** @@ -323,28 +259,19 @@ public static boolean isDefaultValue(int semantics, double v) { .doubleToInt64Bits(v); } - static int getPersistenceFromInt(int size) { - if (size == 4) - return Persistence.enumInt32; - else if (size == 8) - return Persistence.enumInt64; - else - throw new IllegalArgumentException(); + static boolean isIntegerPersistence(int persistence) { + return persistence >= Persistence.enumInt32; } + static boolean isIntegerSemantics(int semantics) { + return isIntegerPersistence(getPersistence(semantics)); + } + @Override public boolean equals(Object _other) { return (Object) this == _other; } - int calculateHashImpl() { - int v = NumberUtils.hash(m_semantics[0]); - for (int i = 1; i < m_attributeCount; i++) - v = NumberUtils.hash(v, m_semantics[i]); - - return v; // if attribute size is 1, it returns 0 - } - /** * * Returns a packed array of double representation of all ordinates of @@ -373,19 +300,81 @@ int _getPointAttributeOffsetFromSemantics(int semantics) { return m_pointAttributeOffsets[getAttributeIndex(semantics)]; } - int _getTotalComponents() { - return m_defaultPointAttributes.length; - } - @Override public int hashCode() { return m_hash; } int _getSemanticsImpl(int attributeIndex) { - return m_semantics[attributeIndex]; + return m_indexToSemantics[attributeIndex]; + } + + VertexDescription(int bitMask) { + m_semanticsBitArray = bitMask; + m_attributeCount = 0; + m_totalComponentCount = 0; + m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS + 1]; + Arrays.fill(m_semanticsToIndexMap, -1); + for (int i = 0, flag = 1, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { + if ((bitMask & flag) != 0) { + m_semanticsToIndexMap[i] = m_attributeCount; + m_attributeCount++; + int comps = getComponentCount(i); + m_totalComponentCount += comps; + } + + flag <<= 1; + } + + m_indexToSemantics = new int[m_attributeCount]; + for (int i = 0, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { + int attrib = m_semanticsToIndexMap[i]; + if (attrib >= 0) + m_indexToSemantics[attrib] = i; + } + + m_defaultPointAttributes = new double[m_totalComponentCount]; + m_pointAttributeOffsets = new int[m_attributeCount]; + int offset = 0; + for (int i = 0, n = m_attributeCount; i < n; i++) { + int semantics = getSemantics(i); + int comps = getComponentCount(semantics); + double v = getDefaultValue(semantics); + m_pointAttributeOffsets[i] = offset; + for (int icomp = 0; icomp < comps; icomp++) { + m_defaultPointAttributes[offset] = v; + offset++; + } + } + + m_hash = NumberUtils.hash(m_semanticsBitArray); } - // TODO: clone, equald, hashcode - whats really needed? + private int m_attributeCount; + int m_semanticsBitArray; //the main component + private int m_totalComponentCount; + private int m_hash; + + private int[] m_semanticsToIndexMap; + private int[] m_indexToSemantics; + private int[] m_pointAttributeOffsets; + private double[] m_defaultPointAttributes; + + static final double[] _defaultValues = { 0, 0, NumberUtils.NaN(), 0, 0, 0, + 0, 0, 0 }; + + static final int[] _interpolation = { Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.NONE, + Interpolation.ANGULAR, Interpolation.LINEAR, Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.NONE, }; + + static final int[] _persistence = { Persistence.enumDouble, + Persistence.enumDouble, Persistence.enumDouble, + Persistence.enumInt32, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumInt32, }; + + static final int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; + static final int[] _components = { 2, 1, 1, 1, 3, 1, 2, 3, 2, }; } diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 268b75bb..c6d69b15 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -32,188 +32,53 @@ * This factory class allows to describe and create a VertexDescription * instance. */ -class VertexDescriptionDesignerImpl extends VertexDescription { - - /** - * Designer default constructor produces XY vertex description (POSITION - * semantics only). - */ - public VertexDescriptionDesignerImpl() { - super(); - m_semantics = new int[Semantics.MAXSEMANTICS]; - m_semantics[0] = Semantics.POSITION; - m_attributeCount = 1; - - m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS]; - - for (int i = 0; i < Semantics.MAXSEMANTICS; i++) - m_semanticsToIndexMap[i] = -1; - - m_semanticsToIndexMap[m_semantics[0]] = 0; - - m_bModified = true; - } - - /** - * Creates description designer and initializes it from the given - * description. Use this to add or remove attributes from the description. - */ - public VertexDescriptionDesignerImpl(VertexDescription other) { - super(other.hashCode(), other); - m_bModified = true; - } - - /** - * Adds a new attribute to the VertexDescription. - * - * @param semantics - * Attribute semantics. - */ - public void addAttribute(int semantics) { - if (hasAttribute(semantics)) - return; +final class VertexDescriptionDesignerImpl { + static VertexDescription getVertexDescription(int descriptionBitMask) { + return VertexDescriptionHash.getInstance() + .FindOrAdd(descriptionBitMask); + } + + static VertexDescription getMergedVertexDescription( + VertexDescription descr1, VertexDescription descr2) { + int mask = descr1.m_semanticsBitArray | descr2.m_semanticsBitArray; + if ((mask & descr1.m_semanticsBitArray) == mask) { + return descr1; + } else if ((mask & descr2.m_semanticsBitArray) == mask) { + return descr2; + } - m_semanticsToIndexMap[semantics] = 0;// assign a value >= 0 to mark it - // as existing - _initMapping(); + return getVertexDescription(mask); } - /** - * Removes given attribute. - * - * @param semantics - * Attribute semantics. - */ - void removeAttribute(int semantics) { - - if (semantics == Semantics.POSITION) - throw new IllegalArgumentException( - "Position attribue cannot be removed");// not allowed to - // remove the xy - - if (!hasAttribute(semantics)) - return; + static VertexDescription getMergedVertexDescription( + VertexDescription descr, int semantics) { + int mask = descr.m_semanticsBitArray | (1 << semantics); + if ((mask & descr.m_semanticsBitArray) == mask) { + return descr; + } - m_semanticsToIndexMap[semantics] = -1;// assign a value < 0 to mark it - // as removed - _initMapping(); + return getVertexDescription(mask); } - /** - * Removes all attributes from the designer with exception of the POSITION - * attribute. - */ - public void reset() { - m_semantics[0] = Semantics.POSITION; - m_attributeCount = 1; - - for (int i : m_semanticsToIndexMap) - m_semanticsToIndexMap[i] = -1; - - m_semanticsToIndexMap[m_semantics[0]] = 0; - m_bModified = true; - } + static VertexDescription removeSemanticsFromVertexDescription( + VertexDescription descr, int semanticsToRemove) { + int mask = (descr.m_semanticsBitArray | (1 << (int) semanticsToRemove)) + - (1 << (int) semanticsToRemove); + if (mask == descr.m_semanticsBitArray) { + return descr; + } - /** - * Returns a VertexDescription corresponding to the vertex design.
- * Note: the same instance of VertexDescription will be returned each time - * for the same same set of attributes and attribute properties.
- * The method searches for the VertexDescription in a global hash table. If - * found, it is returned. Else, a new instance of the VertexDescription is - * added to the has table and returned. - */ - public VertexDescription getDescription() { - VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); - VertexDescriptionDesignerImpl vdd = this; - return vdhash.add(vdd); + return getVertexDescription(mask); } - /** - * Returns a default VertexDescription that has X and Y coordinates only. - */ static VertexDescription getDefaultDescriptor2D() { - VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); - VertexDescription vd = vdhash.getVD2D(); - return vd; + return VertexDescriptionHash.getInstance().getVD2D(); } - /** - * Returns a default VertexDescription that has X, Y, and Z coordinates only - */ static VertexDescription getDefaultDescriptor3D() { - VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); - VertexDescription vd = vdhash.getVD3D(); - return vd; - } - - VertexDescription _createInternal() { - int hash = hashCode(); - VertexDescription vd = new VertexDescription(hash, this); - return vd; - } - - protected boolean m_bModified; - - protected void _initMapping() { - m_attributeCount = 0; - for (int i = 0, j = 0; i < Semantics.MAXSEMANTICS; i++) { - if (m_semanticsToIndexMap[i] >= 0) { - m_semantics[j] = i; - m_semanticsToIndexMap[i] = j; - j++; - m_attributeCount++; - } - } - - m_bModified = true; - } - - @Override - public int hashCode() { - if (m_bModified) { - m_hash = calculateHashImpl(); - m_bModified = false; - } - - return m_hash; - } - - @Override - public boolean equals(Object _other) { - if (_other == null) - return false; - if (_other == this) - return true; - if (_other.getClass() != getClass()) - return false; - VertexDescriptionDesignerImpl other = (VertexDescriptionDesignerImpl) (_other); - if (other.getAttributeCount() != getAttributeCount()) - return false; - - for (int i = 0; i < m_attributeCount; i++) { - if (m_semantics[i] != other.m_semantics[i]) - return false; - } - if (m_bModified != other.m_bModified) - return false; - - return true; + return VertexDescriptionHash.getInstance().getVD3D(); } - public boolean isDesignerFor(VertexDescription vd) { - if (vd.getAttributeCount() != getAttributeCount()) - return false; - - for (int i = 0; i < m_attributeCount; i++) { - if (m_semantics[i] != vd.m_semantics[i]) - return false; - } - - return true; - } - - // returns a mapping from the source attribute indices to the destination - // attribute indices. static int[] mapAttributes(VertexDescription src, VertexDescription dest) { int[] srcToDst = new int[src.getAttributeCount()]; Arrays.fill(srcToDst, -1); @@ -222,41 +87,4 @@ static int[] mapAttributes(VertexDescription src, VertexDescription dest) { } return srcToDst; } - - static VertexDescription getMergedVertexDescription(VertexDescription src, - int semanticsToAdd) { - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - src); - vdd.addAttribute(semanticsToAdd); - return vdd.getDescription(); - } - - static VertexDescription getMergedVertexDescription(VertexDescription d1, VertexDescription d2) { - VertexDescriptionDesignerImpl vdd = null; - for (int semantics = Semantics.POSITION; semantics < Semantics.MAXSEMANTICS; semantics++) { - if (!d1.hasAttribute(semantics) && d2.hasAttribute(semantics)) { - if (vdd == null) { - vdd = new VertexDescriptionDesignerImpl(d1); - } - - vdd.addAttribute(semantics); - } - } - - if (vdd != null) { - return vdd.getDescription(); - } - - return d1; - } - - static VertexDescription removeSemanticsFromVertexDescription( - VertexDescription src, int semanticsToRemove) { - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - src); - vdd.removeAttribute(semanticsToRemove); - return vdd.getDescription(); - } - } - diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java index dfe372aa..8e12dfec 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java @@ -25,9 +25,11 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * A hash object singleton that stores all VertexDescription instances via @@ -35,75 +37,45 @@ * VertexDescription instances to prevent duplicates. */ final class VertexDescriptionHash { - Map> map = new HashMap>(); - - private static VertexDescription m_vd2D; + HashMap m_map = new HashMap(); - private static VertexDescription m_vd3D; + private static VertexDescription m_vd2D = new VertexDescription(1); + private static VertexDescription m_vd3D = new VertexDescription(3); private static final VertexDescriptionHash INSTANCE = new VertexDescriptionHash(); private VertexDescriptionHash() { - VertexDescriptionDesignerImpl vdd2D = new VertexDescriptionDesignerImpl(); - add(vdd2D); - VertexDescriptionDesignerImpl vdd3D = new VertexDescriptionDesignerImpl(); - vdd3D.addAttribute(Semantics.Z); - add(vdd3D); + m_map.put(1, m_vd2D); + m_map.put(3, m_vd3D); } public static VertexDescriptionHash getInstance() { return INSTANCE; } - public VertexDescription getVD2D() { + public final VertexDescription getVD2D() { return m_vd2D; } - public VertexDescription getVD3D() { + public final VertexDescription getVD3D() { return m_vd3D; } - synchronized public VertexDescription add(VertexDescriptionDesignerImpl vdd) { - // Firstly quick test for 2D/3D descriptors. - int h = vdd.hashCode(); - - if ((m_vd2D != null) && m_vd2D.hashCode() == h) { - if (vdd.isDesignerFor(m_vd2D)) - return m_vd2D; - } - - if ((m_vd3D != null) && (m_vd3D.hashCode() == h)) { - if (vdd.isDesignerFor(m_vd3D)) - return m_vd3D; - } - - // Now search in the hash. - - VertexDescription vd = null; - if (map.containsKey(h)) { - WeakReference vdweak = map.get(h); - vd = vdweak.get(); - if (vd == null) // GC'd VertexDescription - map.remove(h); - } - - if (vd == null) { // either not in map to begin with, or has been GC'd - vd = vdd._createInternal(); - - if (vd.getAttributeCount() == 1) { - m_vd2D = vd; - } else if ((vd.getAttributeCount() == 2) - && (vd.getSemantics(1) == Semantics.Z)) { - m_vd3D = vd; - } else { - WeakReference vdweak = new WeakReference( - vd); - - map.put(h, vdweak); + public final VertexDescription FindOrAdd(int bitSet) { + if (bitSet == 1) + return m_vd2D; + if (bitSet == 3) + return m_vd3D; + + synchronized (this) { + VertexDescription vd = m_map.get(bitSet); + if (vd == null) { + vd = new VertexDescription(bitSet); + m_map.put(bitSet, vd); } + return vd; } - - return vd; } + } diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index c3b4894e..7f79ed5f 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -222,7 +222,7 @@ private void geometry_() { m_function_stack.removeLast(); if (m_start_token + 5 <= m_wkt_string.length() - && m_wkt_string.regionMatches(true, m_start_token, "points", 0, + && m_wkt_string.regionMatches(true, m_start_token, "point", 0, 5)) { m_end_token = m_start_token + 5; m_current_token_type = WktToken.point; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index b87a570a..1e4cb7be 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -3,13 +3,11 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.NumberUtils; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.Operator; -import com.esri.core.geometry.JsonCursor; -import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -152,6 +150,40 @@ public ByteBuffer asBinary() { return wkbBuffer; } + @Override + public String asGeoJson() { + return asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportDefaults); + } + + @Override + String asGeoJsonImpl(int export_flags) { + StringBuilder sb = new StringBuilder(); + + sb.append("{\"type\":\"GeometryCollection\",\"geometries\":"); + + sb.append("["); + for (int i = 0, n = numGeometries(); i < n; i++) { + if (i > 0) + sb.append(","); + + if (geometryN(i) != null) + sb.append(geometryN(i).asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportSkipCRS)); + } + + sb.append("],\"crs\":"); + + if (esriSR != null) { + String crs_value = OperatorExportToGeoJson.local().exportSpatialReference(0, esriSR); + sb.append(crs_value); + } else { + sb.append("\"null\""); + } + + sb.append("}"); + + return sb.toString(); + } + @Override public boolean isEmpty() { return numGeometries() == 0; @@ -319,8 +351,7 @@ public void setSpatialReference(SpatialReference esriSR_) { } @Override - public OGCGeometry convertToMulti() - { + public OGCGeometry convertToMulti() { return this; } @@ -330,32 +361,44 @@ public String asJson() { } @Override - public String asGeoJson() { - StringBuilder sb = new StringBuilder(); - - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - JsonCursor cursor = op.execute(this.esriSR, getEsriGeometryCursor()); - - sb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : "); - String shape = cursor.next(); - if (shape == null){ - // geometry collection with empty list of geometries - sb.append("[]}"); - return sb.toString(); + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + OGCConcreteGeometryCollection another = (OGCConcreteGeometryCollection)other; + if (geometries != null) { + if (!geometries.equals(another.geometries)) + return false; } - - sb.append("["); - sb.append(shape); - - while(true){ - shape = cursor.next(); - if(shape == null) - break; - sb.append(", ").append(shape); + else if (another.geometries != null) + return false; + + if (esriSR == another.esriSR) { + return true; } + + if (esriSR != null && another.esriSR != null) { + return esriSR.equals(another.esriSR); + } + + return false; + } - sb.append("]}"); - return sb.toString(); + @Override + public int hashCode() { + int hash = 1; + if (geometries != null) + hash = geometries.hashCode(); + + if (esriSR != null) + hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); + + return hash; } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 9783bb94..c0a93ce2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -12,6 +12,7 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Envelope1D; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryCursorAppend; @@ -19,6 +20,7 @@ import com.esri.core.geometry.MapGeometry; import com.esri.core.geometry.MapOGCStructure; import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.NumberUtils; import com.esri.core.geometry.OGCStructure; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorBuffer; @@ -93,7 +95,12 @@ public ByteBuffer asBinary() { public String asGeoJson() { OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(getEsriGeometry()); + return op.execute(esriSR, getEsriGeometry()); + } + + String asGeoJsonImpl(int export_flags) { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(export_flags, esriSR, getEsriGeometry()); } /** @@ -489,7 +496,7 @@ public static OGCGeometry fromEsriShape(ByteBuffer buffer) { } public static OGCGeometry fromJson(String string) - throws JsonParseException, IOException { + throws Exception { JsonFactory factory = new JsonFactory(); JsonParser jsonParserPt = factory.createJsonParser(string); jsonParserPt.nextToken(); @@ -498,7 +505,8 @@ public static OGCGeometry fromJson(String string) mapGeom.getSpatialReference()); } - public static OGCGeometry fromGeoJson(String string) throws JSONException { + public static OGCGeometry fromGeoJson(String string) + throws Exception { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); @@ -679,4 +687,51 @@ public String toString() { return String .format("%s: %s", this.getClass().getSimpleName(), snippet); } -} \ No newline at end of file + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + OGCGeometry another = (OGCGeometry)other; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + + if (geom1 == null) { + if (geom2 != null) + return false; + } + else if (!geom1.equals(geom2)) { + return false; + } + + if (esriSR == another.esriSR) { + return true; + } + + if (esriSR != null && another.esriSR != null) { + return esriSR.equals(another.esriSR); + } + + return false; + } + + @Override + public int hashCode() { + int hash = 1; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + if (geom1 != null) + hash = geom1.hashCode(); + + if (esriSR != null) + hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); + + return hash; + } +} diff --git a/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt b/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt index 7cb8d997..86477d47 100644 --- a/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt +++ b/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt @@ -284,6 +284,7 @@ 3775 102186 3776 102187 3777 102188 +3785 102113 3800 102183 3801 102189 3812 102199 diff --git a/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt b/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt index f38609e5..e4386fc5 100644 --- a/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt +++ b/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt @@ -3126,6 +3126,7 @@ 102110 3 102111 3 102112 3 +102113 3 102114 3 102115 3 102116 3 diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index 0149d482..9653e5fa 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -107,7 +107,7 @@ public static void testClipGeometries() { } } - public static Polygon makePolygon() { + static Polygon makePolygon() { Polygon poly = new Polygon(); poly.startPath(0, 0); poly.lineTo(10, 10); @@ -116,7 +116,7 @@ public static Polygon makePolygon() { return poly; } - public static Polyline makePolyline() { + static Polyline makePolyline() { Polyline poly = new Polyline(); poly.startPath(0, 0); poly.lineTo(10, 10); @@ -183,7 +183,7 @@ public static void testArcObjectsFailureCR196492() { // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); } - public static Polyline makePolylineCR() { + static Polyline makePolylineCR() { Polyline polyline = new Polyline(); polyline.startPath(-200, -90); @@ -197,7 +197,7 @@ public static Polyline makePolylineCR() { return polyline; } - public static MultiPoint makeMultiPoint() { + static MultiPoint makeMultiPoint() { MultiPoint mpoint = new MultiPoint(); Point2D pt1 = new Point2D(); @@ -219,7 +219,7 @@ public static MultiPoint makeMultiPoint() { return mpoint; } - public static Point makePoint() { + static Point makePoint() { Point point = new Point(); Point2D pt = new Point2D(); diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index 72b6198a..62c59c2f 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -14,12 +14,45 @@ protected void tearDown() throws Exception { super.tearDown(); } + @Test + public static void testFewPoints() { + { + Polygon polygon = new Polygon(); + polygon.addPath((Point2D[]) null, 0, true); + polygon.insertPoint(0, -1, Point2D.construct(5, 5)); + + Point convex_hull = (Point) OperatorConvexHull.local().execute(polygon, null); + assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); + } + + { + Point2D[] pts = new Point2D[3]; + + pts[0] = Point2D.construct(0, 0); + pts[1] = Point2D.construct(0, 0); + pts[2] = Point2D.construct(0, 0); + + int[] out_pts = new int[3]; + int res = ConvexHull.construct(pts, 3, out_pts); + assertTrue(res == 1); + assertTrue(out_pts[0] == 0); + } + + { + Point2D[] pts = new Point2D[1]; + pts[0] = Point2D.construct(0, 0); + + int[] out_pts = new int[1]; + int res = ConvexHull.construct(pts, 1, out_pts); + assertTrue(res == 1); + assertTrue(out_pts[0] == 0); + } + } + @Test public static void testDegenerate() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); { Polygon polygon = new Polygon(); @@ -38,7 +71,7 @@ public static void testDegenerate() { polygon.lineTo(3, 0); Polygon densified = (Polygon) (densify.execute(polygon, .5, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(convex_hull.calculateArea2D() == 0.0); @@ -232,21 +265,19 @@ public static void testDegenerate() { mpoint.add(4, 4); mpoint.add(4, 4); - Polygon convex_hull = (Polygon) (bounding.execute(mpoint, null)); - assertTrue(convex_hull.getPointCount() == 2); + Point convex_hull = (Point) bounding.execute(mpoint, null); assertTrue(convex_hull.calculateArea2D() == 0.0); assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); } { MultiPoint mpoint = new MultiPoint(); mpoint.add(4, 4); - MultiPoint convex_hull = (MultiPoint) (bounding.execute(mpoint, - null)); - assertTrue(convex_hull.getPointCount() == 1); + Point convex_hull = (Point) bounding.execute(mpoint, null); assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull == mpoint); + assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); } { @@ -254,7 +285,7 @@ public static void testDegenerate() { mpoint.add(4, 4); mpoint.add(4, 5); - Polyline convex_hull = (Polyline) (bounding.execute(mpoint, null)); + Polyline convex_hull = (Polyline) bounding.execute(mpoint, null); assertTrue(convex_hull.getPointCount() == 2); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(convex_hull.calculateLength2D() == 1.0); @@ -265,7 +296,7 @@ public static void testDegenerate() { line.setStartXY(0, 0); line.setEndXY(0, 1); - Polyline convex_hull = (Polyline) (bounding.execute(line, null)); + Polyline convex_hull = (Polyline) bounding.execute(line, null); assertTrue(convex_hull.getPointCount() == 2); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(convex_hull.calculateLength2D() == 1.0); @@ -276,7 +307,7 @@ public static void testDegenerate() { polyline.startPath(0, 0); polyline.lineTo(0, 1); - Polyline convex_hull = (Polyline) (bounding.execute(polyline, null)); + Polyline convex_hull = (Polyline) bounding.execute(polyline, null); assertTrue(convex_hull.getPointCount() == 2); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(polyline == convex_hull); @@ -285,23 +316,81 @@ public static void testDegenerate() { { Envelope env = new Envelope(0, 0, 10, 10); + assertTrue(OperatorConvexHull.local().isConvex(env, null)); + + Polygon convex_hull = (Polygon) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 4); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + assertTrue(convex_hull.getXY(2).equals(Point2D.construct(10, 10))); + assertTrue(convex_hull.getXY(3).equals(Point2D.construct(10, 0))); + } - Envelope convex_hull = (Envelope) (bounding.execute(env, null)); + { + Envelope env = new Envelope(0, 0, 0, 10); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Polyline convex_hull = (Polyline) bounding.execute(env, null); assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(env == convex_hull); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + } + + { + Envelope env = new Envelope(0, 0, 0, 10); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Polyline convex_hull = (Polyline) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + } + + { + Envelope env = new Envelope(5, 5, 5, 5); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Point convex_hull = (Point) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); } } + @Test + public static void testSegment() { + { + Line line = new Line(); + line.setStartXY(5, 5); + line.setEndXY(5, 5); + + assertTrue(!OperatorConvexHull.local().isConvex(line, null)); + Point point = (Point) OperatorConvexHull.local().execute(line, null); + assertTrue(point.getXY().equals(Point2D.construct(5, 5))); + } + + { + Line line = new Line(); + line.setStartXY(5, 5); + line.setEndXY(5, 6); + + assertTrue(OperatorConvexHull.local().isConvex(line, null)); + Polyline polyline = (Polyline) OperatorConvexHull.local().execute(line, null); + assertTrue(polyline.getPointCount() == 2); + assertTrue(polyline.getXY(0).equals(Point2D.construct(5, 5))); + assertTrue(polyline.getXY(1).equals(Point2D.construct(5, 6))); + } + } + + @Test public static void testSquare() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); Polygon square = new Polygon(); square.startPath(0, 0); @@ -330,9 +419,13 @@ public static void testSquare() { square.lineTo(2, 1); Polygon densified = (Polygon) (densify.execute(square, 1.0, null)); + + densified.addAttribute(VertexDescription.Semantics.ID); + for (int i = 0; i < densified.getPointCount(); i++) + densified.setAttribute(VertexDescription.Semantics.ID, i, 0, i); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); @@ -340,32 +433,23 @@ public static void testSquare() { @Test public static void testPolygons() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}") - .getGeometry()); + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); - Polygon differenced = (Polygon) (difference.execute(shape, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -376,196 +460,144 @@ public static void testPolygons() { polygon.lineTo(-1, 0); polygon.lineTo(0, -1); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); // assertTrue(bounding.isConvex(*convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -573,14 +605,10 @@ public static void testPolygons() { @Test public static void testPolylines() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); { Polyline poly = new Polyline(); @@ -591,9 +619,8 @@ public static void testPolylines() { poly.lineTo(0, 500); Polyline densified = (Polyline) (densify.execute(poly, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polyline differenced = (Polyline) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); + Polyline differenced = (Polyline) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -608,11 +635,9 @@ public static void testPolylines() { polyline.lineTo(170, 45); polyline.lineTo(225, 65); - Polyline densified = (Polyline) (densify.execute(polyline, 10.0, - null)); + Polyline densified = (Polyline) (densify.execute(polyline, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - boolean bcontains = contains.execute(convex_hull, densified, - SpatialReference.create(4326), null); + boolean bcontains = contains.execute(convex_hull, densified, SpatialReference.create(4326), null); assertTrue(bcontains); assertTrue(bounding.isConvex(convex_hull, null)); @@ -621,46 +646,33 @@ public static void testPolylines() { @Test public static void testNonSimpleShape() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains); - - { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}") - .getGeometry()); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + { + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}") - .getGeometry()); + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}") - .getGeometry()); + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); - Polygon differenced = (Polygon) (difference.execute(shape, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -725,10 +737,8 @@ public static void testNonSimpleShape() { @Test public static void testStar() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); Polygon star = new Polygon(); star.startPath(0, 0); @@ -797,8 +807,7 @@ public static void testPointsArray() { @Test public static void testMergeCursor() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); Polygon geom1 = new Polygon(); Polygon geom2 = new Polygon(); @@ -890,7 +899,7 @@ public void testHullTickTock() { Polygon geom1 = new Polygon(); Polygon geom2 = new Polygon(); Point geom3 = new Point(); - Line geom4= new Line(); + Line geom4 = new Line(); Envelope geom5 = new Envelope(); MultiPoint geom6 = new MultiPoint(); @@ -943,7 +952,7 @@ public void testHullTickTock() { ticktock.tock(); // Get the result Geometry result = ticktock.next(); - Polygon convex_hull = (Polygon)result; + Polygon convex_hull = (Polygon) result; assertTrue(OperatorConvexHull.local().isConvex(convex_hull, null)); Point2D p1 = convex_hull.getXY(0); @@ -959,5 +968,5 @@ public void testHullTickTock() { assertTrue(p5.x == -5.0 && p5.y == 1.25); assertTrue(p6.x == 0.0 && p6.y == 10.0); } - + } diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index ca7bc3d4..455877e6 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -1,6 +1,10 @@ package com.esri.core.geometry; +import java.util.ArrayList; +import java.util.List; + import junit.framework.TestCase; + import org.junit.Test; public class TestDifference extends TestCase { diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 0cdbf6f7..8777ec19 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -24,7 +24,7 @@ public void testTriangleLength() { length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-12 * 3744719.4094597572); } @Test @@ -36,7 +36,7 @@ public void testRotationInvariance() { length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); for (int i = -540; i < 540; i += 5) { pt_0.setXY(i + 10, 40); @@ -46,10 +46,41 @@ public void testRotationInvariance() { length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); } } + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + @Test public void testLengthAccurateCR191313() { /* diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 9c517d39..5ef3157e 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -3,7 +3,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -32,6 +32,7 @@ import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; import org.json.JSONException; +import org.json.JSONObject; import org.junit.Test; import java.io.IOException; @@ -39,368 +40,432 @@ import java.util.List; public class TestGeomToGeoJson extends TestCase { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPoint() { - Point p = new Point(10.0, 20.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); - } - - @Test - public void testEmptyPoint() { - Point p = new Point(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":null}", result); - } - - @Test - public void testPointGeometryEngine() { - Point p = new Point(10.0, 20.0); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); - } - - @Test - public void testOGCPoint() { - Point p = new Point(10.0, 20.0); - OGCGeometry ogcPoint = new OGCPoint(p, null); - String result = ogcPoint.asGeoJson(); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); - } - - @Test - public void testMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); - } - - @Test - public void testEmptyMultiPoint() { - MultiPoint mp = new MultiPoint(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":null}", result); - } - - @Test - public void testMultiPointGeometryEngine() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - String result = GeometryEngine.geometryToGeoJson(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); - } - - @Test - public void testOGCMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); - String result = ogcMultiPoint.asGeoJson(); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); - } - - @Test - public void testPolyline() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); - } - - @Test - public void testEmptyPolyline() { - Polyline p = new Polyline(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":null}", result); - } - - @Test - public void testPolylineGeometryEngine() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); - } - - @Test - public void testOGCLineString() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - OGCLineString ogcLineString = new OGCLineString(p, 0, null); - String result = ogcLineString.asGeoJson(); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); - } - - @Test - public void testPolygon() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); - } - - @Test - public void testPolygonWithHole() { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - } - - @Test - public void testPolygonWithHoleReversed() { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); - } - - @Test - public void testMultiPolygon() throws IOException { - JsonFactory jsonFactory = new JsonFactory(); - - String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100.0,-100.0],[-100.0,100.0],[100.0,100.0],[100.0,-100.0],[-100.0,-100.0]],[[-90.0,-90.0],[90.0,90.0],[-90.0,90.0],[90.0,-90.0],[-90.0,-90.0]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; - String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; - - JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); - MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); - //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); - - Polygon poly = (Polygon) parsedPoly.getGeometry(); - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - //String result = exporter.execute(parsedPoly.getGeometry()); - String result = exporter.execute(poly); - assertEquals(geoJsonPolygon, result); - } - - - - @Test - public void testEmptyPolygon() throws JSONException { - Polygon p = new Polygon(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":null}", result); - - MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); - assertTrue(imported.getGeometry().isEmpty()); - assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); - } - - @Test - public void testPolygonGeometryEngine() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); - } - - @Test - public void testOGCPolygon() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - OGCPolygon ogcPolygon = new OGCPolygon(p, null); - String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); - } - - @Test - public void testPolygonWithHoleGeometryEngine() { - Polygon p = new Polygon(); - - p.startPath(100.0, 0.0);//clockwise exterior - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - p.startPath(100.2, 0.2);//counterclockwise hole - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - } - - @Test - public void testPolylineWithTwoPaths() { - Polyline p = new Polyline(); - - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100.0,0.0],[100.0,1.0]],[[100.2,0.2],[100.8,0.2]]]}", result); - } - - @Test - public void testOGCPolygonWithHole() { - Polygon p = new Polygon(); - - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - OGCPolygon ogcPolygon = new OGCPolygon(p, null); - String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - } - - @Test - public void testEnvelope() { - Envelope e = new Envelope(); - e.setCoords(-180.0, -90.0, 180.0, 90.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(e); - assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); - } - - @Test - public void testEmptyEnvelope() { - Envelope e = new Envelope(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(e); - assertEquals("{\"bbox\":null}", result); - } - - @Test - public void testEnvelopeGeometryEngine() { - Envelope e = new Envelope(); - e.setCoords(-180.0, -90.0, 180.0, 90.0); - String result = GeometryEngine.geometryToGeoJson(e); - assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); - } - - @Test - public void testGeometryCollection(){ - SpatialReference sr = SpatialReference.create(4326); - - StringBuilder geometrySb = new StringBuilder(); - geometrySb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); - - OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); - assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", point.asJson()); - assertEquals("{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}", point.asGeoJson()); - geometrySb.append(point.asGeoJson()).append(", "); - - OGCLineString line = new OGCLineString(new Polyline(new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); - assertEquals("{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", line.asJson()); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[2.0,2.0]]}", line.asGeoJson()); - geometrySb.append(line.asGeoJson()).append(", "); - - Polygon p = new Polygon(); - p.startPath(1.0, 1.0); - p.lineTo(2.0, 2.0); - p.lineTo(3.0, 1.0); - p.lineTo(2.0, 0.0); - - OGCPolygon polygon = new OGCPolygon(p, sr); - assertEquals("{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", - polygon.asJson()); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[2.0,2.0],[3.0,1.0],[2.0,0.0],[1.0,1.0]]]}", - polygon.asGeoJson()); - geometrySb.append(polygon.asGeoJson()).append("]}"); - - List geoms = new ArrayList(3); - geoms.add(point);geoms.add(line);geoms.add(polygon); - OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(geoms, sr); - assertEquals(geometrySb.toString(), collection.asGeoJson()); - } - - @Test - public void testEmptyGeometryCollection(){ - SpatialReference sr = SpatialReference.create(4326); - OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(new ArrayList(), sr); - assertEquals("{\"type\" : \"GeometryCollection\", \"geometries\" : []}", collection.asGeoJson()); - } + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPoint() { + Point p = new Point(10.0, 20.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); + } + + @Test + public void testEmptyPoint() { + Point p = new Point(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); + } + + @Test + public void testPointGeometryEngine() { + Point p = new Point(10.0, 20.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); + } + + @Test + public void testOGCPoint() { + Point p = new Point(10.0, 20.0); + OGCGeometry ogcPoint = new OGCPoint(p, null); + String result = ogcPoint.asGeoJson(); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20],\"crs\":null}", result); + } + + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); + } + + @Test + public void testEmptyMultiPoint() { + MultiPoint mp = new MultiPoint(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); + } + + @Test + public void testMultiPointGeometryEngine() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + String result = GeometryEngine.geometryToGeoJson(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); + } + + @Test + public void testOGCMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); + String result = ogcMultiPoint.asGeoJson(); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]],\"crs\":null}", result); + } + + @Test + public void testPolyline() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); + } + + @Test + public void testEmptyPolyline() { + Polyline p = new Polyline(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); + } + + @Test + public void testPolylineGeometryEngine() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); + } + + @Test + public void testOGCLineString() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OGCLineString ogcLineString = new OGCLineString(p, 0, null); + String result = ogcLineString.asGeoJson(); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]],\"crs\":null}", result); + } + + @Test + public void testPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); + } + + @Test + public void testPolygonWithHole() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + + @Test + public void testPolygonWithHoleReversed() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + } + + @Test + public void testMultiPolygon() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + + String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; + String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + + JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); + MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); + //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); + + Polygon poly = (Polygon) parsedPoly.getGeometry(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + //String result = exporter.execute(parsedPoly.getGeometry()); + String result = exporter.execute(poly); + assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]]}", result); + } + + + @Deprecated + @Test + public void testEmptyPolygon() throws JSONException { + Polygon p = new Polygon(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + + MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); + assertTrue(imported.getGeometry().isEmpty()); + assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); + } + + @Test + public void testPolygonGeometryEngine() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); + } + + @Test + public void testOGCPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]],\"crs\":null}", result); + } + + @Test + public void testPolygonWithHoleGeometryEngine() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0);//clockwise exterior + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2);//counterclockwise hole + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + + @Test + public void testPolylineWithTwoPaths() { + Polyline p = new Polyline(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[100,1]],[[100.2,0.2],[100.8,0.2]]]}", result); + } + + @Test + public void testOGCPolygonWithHole() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":null}", result); + } + + @Test + public void testGeometryCollection() { + SpatialReference sr = SpatialReference.create(4326); + + StringBuilder geometrySb = new StringBuilder(); + geometrySb + .append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); + + OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); + assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", + point.asJson()); + assertEquals( + "{\"type\":\"Point\",\"coordinates\":[1,1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + point.asGeoJson()); + geometrySb.append(point.asGeoJson()).append(", "); + + OGCLineString line = new OGCLineString(new Polyline( + new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); + assertEquals( + "{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", + line.asJson()); + assertEquals( + "{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + line.asGeoJson()); + geometrySb.append(line.asGeoJson()).append(", "); + + Polygon p = new Polygon(); + p.startPath(1.0, 1.0); + p.lineTo(2.0, 2.0); + p.lineTo(3.0, 1.0); + p.lineTo(2.0, 0.0); + + OGCPolygon polygon = new OGCPolygon(p, sr); + assertEquals( + "{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", + polygon.asJson()); + assertEquals( + "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + polygon.asGeoJson()); + geometrySb.append(polygon.asGeoJson()).append("]}"); + + List geoms = new ArrayList(3); + geoms.add(point); + geoms.add(line); + geoms.add(polygon); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( + geoms, sr); + String s2 = collection.asGeoJson(); + + JSONObject json = null; + boolean valid = false; + try { + json = new JSONObject(s2); + valid = true; + } catch (Exception e) { + } + + assertTrue(valid); + + assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson()); + } + + @Test + public void testEmptyGeometryCollection() { + SpatialReference sr = SpatialReference.create(4326); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( + new ArrayList(), sr); + String s2 = collection.asGeoJson(); + assertEquals( + "{\"type\":\"GeometryCollection\",\"geometries\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + collection.asGeoJson()); + } + + //Envelope is exported as a polygon (we don't support bbox, as it is not a GeoJson geometry, but simply a field)! + @Test + public void testEnvelope() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = OperatorExportToGeoJson.local().execute(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); + } + + @Test + public void testEmptyEnvelope() { + Envelope e = new Envelope(); + String result = OperatorExportToGeoJson.local().execute(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + } + + @Test + public void testEnvelopeGeometryEngine() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = GeometryEngine.geometryToGeoJson(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); + } + + @Test + public void testOldCRS() throws JSONException { + String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}"; + MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null); + String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry()); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4267\"}}}", result); + } + + // bbox is not supported anymore. + // @Test + // public void testEnvelope() { + // Envelope e = new Envelope(); + // e.setCoords(-180.0, -90.0, 180.0, 90.0); + // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + // String result = exporter.execute(e); + // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + // } + // + // @Test + // public void testEmptyEnvelope() { + // Envelope e = new Envelope(); + // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + // String result = exporter.execute(e); + // assertEquals("{\"bbox\":null}", result); + // } + // + // @Test + // public void testEnvelopeGeometryEngine() { + // Envelope e = new Envelope(); + // e.setCoords(-180.0, -90.0, 180.0, 90.0); + // String result = GeometryEngine.geometryToGeoJson(e); + // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + // } + } diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 99adee7a..1138c082 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -2,11 +2,14 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; + import junit.framework.TestCase; import org.json.JSONException; +import org.json.JSONObject; import org.junit.Test; public class TestImportExport extends TestCase { + @Override protected void setUp() throws Exception { super.setUp(); @@ -19,33 +22,35 @@ protected void tearDown() throws Exception { @Test public static void testImportExportShapePolygon() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); +// { +// String s = "MULTIPOLYGON (((-1.4337158203098852 53.42590083930004, -1.4346462383651897 53.42590083930004, -1.4349713164114632 53.42426406667512, -1.4344808816770183 53.42391134176576, -1.4337158203098852 53.424339319373516, -1.4337158203098852 53.42590083930004, -1.4282226562499147 53.42590083930004, -1.4282226562499147 53.42262754610009, -1.423659941537096 53.42262754610009, -1.4227294921872726 53.42418897437618, -1.4199829101572732 53.42265258737483, -1.4172363281222147 53.42418897437334, -1.4144897460898278 53.42265258737625, -1.4144897460898278 53.42099079900008, -1.4117431640598568 53.42099079712516, -1.4117431640598568 53.41849780932388, -1.4112778948070286 53.41771711805022, -1.4114404909237805 53.41689867267529, -1.411277890108579 53.416080187950215, -1.4117431640598568 53.4152995338453, -1.4117431657531654 53.40953184824072, -1.41723632610001 53.40953184402311, -1.4172363281199125 53.406257299700044, -1.4227294921899158 53.406257299700044, -1.4227294921899158 53.40789459668797, -1.4254760767598498 53.40789460061099, -1.4262193642339867 53.40914148401417, -1.4273828468095076 53.409531853100034, -1.4337158203098852 53.409531790075235, -1.4337158203098852 53.41280609140024, -1.4392089843723568 53.41280609140024, -1.439208984371362 53.41608014067522, -1.441160015802268 53.41935368587538, -1.4427511170075604 53.41935368587538, -1.4447021484373863 53.42099064750012, -1.4501953124999432 53.42099064750012, -1.4501953124999432 53.43214683850347, -1.4513643355446106 53.434108816701794, -1.4502702625278232 53.43636597733034, -1.4494587195580948 53.437354845300334, -1.4431075935937656 53.437354845300334, -1.4372459179209045 53.43244635455021, -1.433996276212838 53.42917388040006, -1.4337158203098852 53.42917388040006, -1.4337158203098852 53.42590083930004)))"; +// Geometry g = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); +// boolean result1 = OperatorSimplify.local().isSimpleAsFeature(g, null, null); +// boolean result2 = OperatorSimplifyOGC.local().isSimpleOGC(g, null, true, null, null); +// Geometry simple = OperatorSimplifyOGC.local().execute(g, null, true, null); +// OperatorFactoryLocal.saveToWKTFileDbg("c:/temp/simplifiedeeee", simple, null); +// int i = 0; +// } + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); Polygon polygon = makePolygon(); byte[] esriShape = GeometryEngine.geometryToEsriShape(polygon); - Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, - Geometry.Type.Unknown); + Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, Geometry.Type.Unknown); TestCommonMethods.compareGeometryContent((MultiPath) imported, polygon); // Test Import Polygon from Polygon ByteBuffer polygonShapeBuffer = exporterShape.execute(0, polygon); - Geometry polygonShapeGeometry = importerShape.execute(0, - Geometry.Type.Polygon, polygonShapeBuffer); + Geometry polygonShapeGeometry = importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); - TestCommonMethods.compareGeometryContent( - (MultiPath) polygonShapeGeometry, polygon); + TestCommonMethods.compareGeometryContent((MultiPath) polygonShapeGeometry, polygon); // Test Import Envelope from Polygon - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, polygonShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polygonShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; - @SuppressWarnings("unused") - Envelope env = new Envelope(), otherenv = new Envelope(); + @SuppressWarnings("unused") Envelope env = new Envelope(), otherenv = new Envelope(); polygon.queryEnvelope(otherenv); assertTrue(envelope.getXMin() == otherenv.getXMin()); assertTrue(envelope.getXMax() == otherenv.getXMax()); @@ -61,25 +66,20 @@ public static void testImportExportShapePolygon() { @Test public static void testImportExportShapePolyline() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); Polyline polyline = makePolyline(); // Test Import Polyline from Polyline ByteBuffer polylineShapeBuffer = exporterShape.execute(0, polyline); - Geometry polylineShapeGeometry = importerShape.execute(0, - Geometry.Type.Polyline, polylineShapeBuffer); + Geometry polylineShapeGeometry = importerShape.execute(0, Geometry.Type.Polyline, polylineShapeBuffer); // TODO test this - TestCommonMethods.compareGeometryContent( - (MultiPath) polylineShapeGeometry, polyline); + TestCommonMethods.compareGeometryContent((MultiPath) polylineShapeGeometry, polyline); // Test Import Envelope from Polyline; - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, polylineShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polylineShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; Envelope env = new Envelope(), otherenv = new Envelope(); @@ -92,32 +92,26 @@ public static void testImportExportShapePolyline() { Envelope1D interval, otherinterval; interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = polyline - .queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = polyline.queryInterval(VertexDescription.Semantics.Z, 0); assertTrue(interval.vmin == otherinterval.vmin); assertTrue(interval.vmax == otherinterval.vmax); } @Test public static void testImportExportShapeMultiPoint() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); MultiPoint multipoint = makeMultiPoint(); // Test Import MultiPoint from MultiPoint ByteBuffer multipointShapeBuffer = exporterShape.execute(0, multipoint); - MultiPoint multipointShapeGeometry = (MultiPoint) importerShape - .execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); - TestCommonMethods.compareGeometryContent( - (MultiPoint) multipointShapeGeometry, multipoint); + TestCommonMethods.compareGeometryContent((MultiPoint) multipointShapeGeometry, multipoint); // Test Import Envelope from MultiPoint - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, multipointShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, multipointShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; Envelope env = new Envelope(), otherenv = new Envelope(); @@ -130,32 +124,27 @@ public static void testImportExportShapeMultiPoint() { Envelope1D interval, otherinterval; interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, - 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, 0); assertTrue(interval.vmin == otherinterval.vmin); assertTrue(interval.vmax == otherinterval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - otherinterval = multipoint.queryInterval( - VertexDescription.Semantics.ID, 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.ID, 0); assertTrue(interval.vmin == otherinterval.vmin); assertTrue(interval.vmax == otherinterval.vmax); } @Test public static void testImportExportShapePoint() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); // Point Point point = makePoint(); // Test Import Point from Point ByteBuffer pointShapeBuffer = exporterShape.execute(0, point); - Point pointShapeGeometry = (Point) importerShape.execute(0, - Geometry.Type.Point, pointShapeBuffer); + Point pointShapeGeometry = (Point) importerShape.execute(0, Geometry.Type.Point, pointShapeBuffer); double x1 = point.getX(); double x2 = pointShapeGeometry.getX(); @@ -178,29 +167,24 @@ public static void testImportExportShapePoint() { assertTrue(id1 == id2); // Test Import Multipoint from Point - MultiPoint multipointShapeGeometry = (MultiPoint) importerShape - .execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); Point point2d = multipointShapeGeometry.getPoint(0); assertTrue(x1 == point2d.getX() && y1 == point2d.getY()); int pointCount = multipointShapeGeometry.getPointCount(); assertTrue(pointCount == 1); - z2 = multipointShapeGeometry.getAttributeAsDbl( - VertexDescription.Semantics.Z, 0, 0); + z2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); assertTrue(z1 == z2); - m2 = multipointShapeGeometry.getAttributeAsDbl( - VertexDescription.Semantics.M, 0, 0); + m2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); assertTrue(m1 == m2); - id2 = multipointShapeGeometry.getAttributeAsInt( - VertexDescription.Semantics.ID, 0, 0); + id2 = multipointShapeGeometry.getAttributeAsInt(VertexDescription.Semantics.ID, 0, 0); assertTrue(id1 == id2); // Test Import Envelope from Point - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, pointShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, pointShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; Envelope env = new Envelope(), otherenv = new Envelope(); @@ -225,17 +209,14 @@ public static void testImportExportShapePoint() { @Test public static void testImportExportShapeEnvelope() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); // Test Export Envelope to Polygon Envelope envelope = makeEnvelope(); ByteBuffer polygonShapeBuffer = exporterShape.execute(0, envelope); - Polygon polygon = (Polygon) importerShape.execute(0, - Geometry.Type.Polygon, polygonShapeBuffer); + Polygon polygon = (Polygon) importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); int pointCount = polygon.getPointCount(); assertTrue(pointCount == 4); @@ -245,26 +226,21 @@ public static void testImportExportShapeEnvelope() { // interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); Point point3d; point3d = polygon.getPoint(0); - assertTrue(point3d.getX() == env.getXMin() - && point3d.getY() == env.getYMin());// && point3d.z == - // interval.vmin); + assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmin); point3d = polygon.getPoint(1); - assertTrue(point3d.getX() == env.getXMin() - && point3d.getY() == env.getYMax());// && point3d.z == - // interval.vmax); + assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmax); point3d = polygon.getPoint(2); - assertTrue(point3d.getX() == env.getXMax() - && point3d.getY() == env.getYMax());// && point3d.z == - // interval.vmin); + assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmin); point3d = polygon.getPoint(3); - assertTrue(point3d.getX() == env.getXMax() - && point3d.getY() == env.getYMin());// && point3d.z == - // interval.vmax); + assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmax); Envelope1D interval; interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, - 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); assertTrue(m == interval.vmin); m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); assertTrue(m == interval.vmax); @@ -274,8 +250,7 @@ public static void testImportExportShapeEnvelope() { assertTrue(m == interval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, - 0, 0); + double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0); assertTrue(id == interval.vmin); id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0); assertTrue(id == interval.vmax); @@ -287,12 +262,10 @@ public static void testImportExportShapeEnvelope() { @Test public static void testImportExportWkbGeometryCollection() { - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order( - ByteOrder.nativeOrder()); + ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order(ByteOrder.nativeOrder()); wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; // byte order wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); @@ -342,7 +315,7 @@ public static void testImportExportWkbGeometryCollection() { wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); offset += 4; wkbBuffer.putInt(offset, 0); // 0 geometries, for empty - // geometrycollection + // geometrycollection offset += 4; wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; @@ -368,8 +341,7 @@ public static void testImportExportWkbGeometryCollection() { offset += 8; // "GeometryCollection( Point (0 0), GeometryCollection( LineString empty, Polygon empty, MultiPolygon empty, MultiLineString empty, MultiPoint empty ), Point (13 17) )"; - OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures - .get(0); + OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures.get(0); assertTrue(structure.m_type == 7); assertTrue(structure.m_structures.get(0).m_type == 1); @@ -395,17 +367,13 @@ public static void testImportExportWkbGeometryCollection() { @Test public static void testImportExportWKBPolygon() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Test Import Polygon with bad rings int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order( - ByteOrder.nativeOrder()); + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; // byte order wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); @@ -505,17 +473,13 @@ public static void testImportExportWKBPolygon() { wkbBuffer.putDouble(offset, 67.0); offset += 8; // y - Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, - null); + Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, null); int pc = ((Polygon) p).getPathCount(); String wktString = exporterWKT.execute(0, p, null); - assertTrue(wktString - .equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); + assertTrue(wktString.equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); - wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, - null); - assertTrue(wktString - .equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, null); + assertTrue(wktString.equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); Polygon polygon = makePolygon(); @@ -523,48 +487,37 @@ public static void testImportExportWKBPolygon() { ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, polygon, null); int wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - Geometry polygonWKBGeometry = importerWKB.execute(0, - Geometry.Type.Polygon, polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polygonWKBGeometry, polygon); + Geometry polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon); // Test WKB_export_multi_polygon on nonempty single part polygon Polygon polygon2 = makePolygon2(); assertTrue(polygon2.getPathCount() == 1); - polygonWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiPolygon, polygon2, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polygonWKBGeometry, polygon2); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); // Test WKB_export_polygon on nonempty single part polygon assertTrue(polygon2.getPathCount() == 1); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, - polygon2, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polygonWKBGeometry, polygon2); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); // Test WKB_export_polygon on empty polygon Polygon polygon3 = new Polygon(); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, - polygon3, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon3, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); assertTrue(polygonWKBGeometry.isEmpty() == true); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygon); // Test WKB_export_defaults on empty polygon polygonWKBBuffer = exporterWKB.execute(0, polygon3, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); assertTrue(polygonWKBGeometry.isEmpty() == true); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygon); @@ -572,18 +525,14 @@ public static void testImportExportWKBPolygon() { @Test public static void testImportExportWKBPolyline() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Test Import Polyline with bad paths (i.e. paths with one point or // zero points) int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order( - ByteOrder.nativeOrder()); + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; // byte order wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); @@ -635,16 +584,14 @@ public static void testImportExportWKBPolyline() { wkbBuffer.putDouble(offset, 88); offset += 8; // y - Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, - wkbBuffer, null)); + Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, wkbBuffer, null)); int pc = p.getPointCount(); int pac = p.getPathCount(); assertTrue(p.getPointCount() == 7); assertTrue(p.getPathCount() == 3); String wktString = exporterWKT.execute(0, p, null); - assertTrue(wktString - .equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); + assertTrue(wktString.equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); Polyline polyline = makePolyline(); polyline.dropAttribute(VertexDescription.Semantics.ID); @@ -653,48 +600,37 @@ public static void testImportExportWKBPolyline() { ByteBuffer polylineWKBBuffer = exporterWKB.execute(0, polyline, null); int wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); - Geometry polylineWKBGeometry = importerWKB.execute(0, - Geometry.Type.Polyline, polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polylineWKBGeometry, polyline); + Geometry polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline); // Test wkbExportMultiPolyline on nonempty single part polyline Polyline polyline2 = makePolyline2(); assertTrue(polyline2.getPathCount() == 1); - polylineWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiLineString, polyline2, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polylineWKBGeometry, polyline2); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); // Test wkbExportPolyline on nonempty single part polyline assertTrue(polyline2.getPathCount() == 1); - polylineWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportLineString, polyline2, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polylineWKBGeometry, polyline2); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbLineStringZM); // Test wkbExportPolyline on empty polyline Polyline polyline3 = new Polyline(); - polylineWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportLineString, polyline3, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline3, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); assertTrue(polylineWKBGeometry.isEmpty() == true); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbLineString); // Test WKB_export_defaults on empty polyline polylineWKBBuffer = exporterWKB.execute(0, polyline3, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); assertTrue(polylineWKBGeometry.isEmpty() == true); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiLineString); @@ -702,53 +638,42 @@ public static void testImportExportWKBPolyline() { @Test public static void testImportExportWKBMultiPoint() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); MultiPoint multipoint = makeMultiPoint(); multipoint.dropAttribute(VertexDescription.Semantics.ID); // Test Import Multi_point from Multi_point - ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, - null); + ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, null); int wkbType = multipointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPointZ); - MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, - Geometry.Type.MultiPoint, multipointWKBBuffer, null)); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) multipointWKBGeometry, multipoint); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) multipointWKBGeometry, multipoint); // Test WKB_export_point on nonempty single point Multi_point MultiPoint multipoint2 = makeMultiPoint2(); assertTrue(multipoint2.getPointCount() == 1); - ByteBuffer pointWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportPoint, multipoint2, null); - Point pointWKBGeometry = (Point) (importerWKB.execute(0, - Geometry.Type.Point, pointWKBBuffer, null)); + ByteBuffer pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); Point3D point3d, mpoint3d; point3d = pointWKBGeometry.getXYZ(); mpoint3d = multipoint2.getXYZ(0); - assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y - && point3d.z == mpoint3d.z); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPointZ); // Test WKB_export_point on empty Multi_point MultiPoint multipoint3 = new MultiPoint(); - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, - multipoint3, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint3, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); assertTrue(pointWKBGeometry.isEmpty() == true); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPoint); // Test WKB_export_defaults on empty Multi_point multipointWKBBuffer = exporterWKB.execute(0, multipoint3, null); - multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, - Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); assertTrue(multipointWKBGeometry.isEmpty() == true); wkbType = multipointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); @@ -756,10 +681,8 @@ public static void testImportExportWKBMultiPoint() { @Test public static void testImportExportWKBPoint() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Point Point point = makePoint(); @@ -768,8 +691,7 @@ public static void testImportExportWKBPoint() { ByteBuffer pointWKBBuffer = exporterWKB.execute(0, point, null); int wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPointZM); - Point pointWKBGeometry = (Point) (importerWKB.execute(0, - Geometry.Type.Point, pointWKBBuffer, null)); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); double x_1 = point.getX(); double x2 = pointWKBGeometry.getX(); @@ -790,27 +712,22 @@ public static void testImportExportWKBPoint() { // Test WKB_export_defaults on empty point Point point2 = new Point(); pointWKBBuffer = exporterWKB.execute(0, point2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); assertTrue(pointWKBGeometry.isEmpty() == true); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPoint); // Test WKB_export_point on empty point - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, - point2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, point2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); assertTrue(pointWKBGeometry.isEmpty() == true); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPoint); // Test WKB_export_multi_point on empty point MultiPoint multipoint = new MultiPoint(); - ByteBuffer multipointWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiPoint, multipoint, null); - MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, - Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + ByteBuffer multipointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPoint, multipoint, null); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); assertTrue(multipointWKBGeometry.isEmpty() == true); wkbType = multipointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); @@ -818,25 +735,20 @@ public static void testImportExportWKBPoint() { // Test WKB_export_point on nonempty single point Multi_point MultiPoint multipoint2 = makeMultiPoint2(); assertTrue(multipoint2.getPointCount() == 1); - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, - multipoint2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); Point3D point3d, mpoint3d; point3d = pointWKBGeometry.getXYZ(); mpoint3d = multipoint2.getXYZ(0); - assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y - && point3d.z == mpoint3d.z); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPointZ); } @Test public static void testImportExportWKBEnvelope() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Test Export Envelope to Polygon (WKB_export_defaults) Envelope envelope = makeEnvelope(); @@ -845,8 +757,7 @@ public static void testImportExportWKBEnvelope() { ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, envelope, null); int wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); - Polygon polygon = (Polygon) (importerWKB.execute(0, - Geometry.Type.Polygon, polygonWKBBuffer, null)); + Polygon polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); int point_count = polygon.getPointCount(); assertTrue(point_count == 4); @@ -857,21 +768,16 @@ public static void testImportExportWKBEnvelope() { interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); Point3D point3d; point3d = polygon.getXYZ(0); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymin - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); point3d = polygon.getXYZ(1); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymax - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); point3d = polygon.getXYZ(2); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymax - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); point3d = polygon.getXYZ(3); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymin - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, - 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); assertTrue(m == interval.vmin); m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); assertTrue(m == interval.vmax); @@ -881,29 +787,23 @@ public static void testImportExportWKBEnvelope() { assertTrue(m == interval.vmax); // Test WKB_export_multi_polygon on nonempty Envelope - polygonWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiPolygon, envelope, null); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, envelope, null); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null)); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); point_count = polygon.getPointCount(); assertTrue(point_count == 4); envelope.queryEnvelope2D(env); interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); point3d = polygon.getXYZ(0); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymin - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); point3d = polygon.getXYZ(1); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymax - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); point3d = polygon.getXYZ(2); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymax - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); point3d = polygon.getXYZ(3); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymin - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); @@ -920,34 +820,28 @@ public static void testImportExportWKBEnvelope() { polygonWKBBuffer = exporterWKB.execute(0, envelope2, null); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygon); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null)); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); assertTrue(polygon.isEmpty()); // Test WKB_export_polygon on empty Envelope - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, - envelope2, null); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, envelope2, null); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygon); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null)); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); assertTrue(polygon.isEmpty()); } @Test public static void testImportExportWktGeometryCollection() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); String wktString; Envelope2D envelope = new Envelope2D(); WktParser wktParser = new WktParser(); wktString = "GeometryCollection( Point (0 0), GeometryCollection( Point (0 0) , Point (1 1) , Point (2 2), LineString empty ), Point (1 1), Point (2 2) )"; - OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures - .get(0); + OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures.get(0); assertTrue(structure.m_type == 7); assertTrue(structure.m_structures.get(0).m_type == 1); @@ -964,10 +858,8 @@ public static void testImportExportWktGeometryCollection() { @Test public static void testImportExportWktMultiPolygon() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); Polygon polygon; String wktString; @@ -975,16 +867,13 @@ public static void testImportExportWktMultiPolygon() { WktParser wktParser = new WktParser(); // Test Import from MultiPolygon - wktString = "Multipolygon M empty"; - polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null); + polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, wktString, null); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); - polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, - Geometry.Type.Unknown); + polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, Geometry.Type.Unknown); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); @@ -996,43 +885,36 @@ public static void testImportExportWktMultiPolygon() { assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); wktString = "Multipolygon Z (empty, (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1)), empty, ((90 90 88, 60 90 7, 60 60 7), empty, (70 70 7, 80 80 7, 70 80 7, 70 70 7)), empty)"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); // assertTrue(polygon.calculate_area_2D() > 0.0); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, - 0); + double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); assertTrue(z == 5); // Test Export to WKT MultiPolygon wktString = exporterWKT.execute(0, polygon, null); - assertTrue(wktString - .equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); + assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } // Test import Polygon wktString = "POLYGON z (EMPTY, EMPTY, (10 10 5, 10 20 5, 20 20 5, 20 10 5), (12 12 3), EMPTY, (10 10 1, 12 12 1), EMPTY, (60 60 7, 60 90 7, 90 90 7, 60 60 7), EMPTY, (70 70 7, 70 80 7, 80 80 7), EMPTY)"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); // Test Export to WKT Polygon - wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, - polygon, null); - assertTrue(wktString - .equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, polygon, null); + assertTrue(wktString.equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1042,16 +924,13 @@ public static void testImportExportWktMultiPolygon() { polygon.queryEnvelope(env); wktString = exporterWKT.execute(0, env, null); - assertTrue(wktString - .equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); + assertTrue(wktString.equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, - env, null); - assertTrue(wktString - .equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1064,44 +943,38 @@ public static void testImportExportWktMultiPolygon() { while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, - env, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); assertTrue(wktString.equals("MULTIPOLYGON Z EMPTY")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = "MULTIPOLYGON (((5 10, 8 10, 10 10, 10 0, 0 0, 0 10, 2 10, 5 10)))"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.calculateArea2D() > 0); wktString = "MULTIPOLYGON Z (((90 10 7, 10 10 1, 10 90 7, 90 90 1, 90 10 7)))"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.getPointCount() == 4); assertTrue(polygon.getPathCount() == 1); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polygon.calculateArea2D() > 0); - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, - polygon, null); - assertTrue(wktString - .equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, polygon, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); } @Test public static void testImportExportWktPolygon() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); // OperatorExportToWkt exporterWKT = // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); @@ -1110,29 +983,24 @@ public static void testImportExportWktPolygon() { Envelope2D envelope = new Envelope2D(); // Test Import from Polygon - wktString = "Polygon ZM empty"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); wktString = "Polygon z (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1))"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polygon.getPointCount() == 8); assertTrue(polygon.getPathCount() == 3); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); wktString = "polygon ((35 10, 10 20, 15 40, 45 45, 35 10), (20 30, 35 35, 30 20, 20 30))"; - Polygon polygon2 = (Polygon) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + Polygon polygon2 = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polygon2 != null); // wktString = exporterWKT.execute(0, *polygon2, null); @@ -1140,8 +1008,7 @@ public static void testImportExportWktPolygon() { @Test public static void testImportExportWktLineString() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); // OperatorExportToWkt exporterWKT = // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); @@ -1150,22 +1017,18 @@ public static void testImportExportWktLineString() { Envelope2D envelope = new Envelope2D(); // Test Import from LineString - wktString = "LineString ZM empty"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); assertTrue(polyline.isEmpty()); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); wktString = "LineString m (10 10 5, 10 20 5, 20 20 5, 20 10 5)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polyline.getPointCount() == 4); assertTrue(polyline.getPathCount() == 1); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); @@ -1173,10 +1036,8 @@ public static void testImportExportWktLineString() { @Test public static void testImportExportWktMultiLineString() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); Polyline polyline; String wktString; @@ -1184,49 +1045,40 @@ public static void testImportExportWktMultiLineString() { WktParser wktParser = new WktParser(); // Test Import from MultiLineString - wktString = "MultiLineStringZMempty"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); assertTrue(polyline.isEmpty()); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); wktString = "MultiLineStringm(empty, empty, (10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3), empty, (10 10 1, 12 12 1), empty, (88 60 7, 60 90 7, 90 90 7), empty, (70 70 7, 70 80 7, 80 80 7), empty)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polyline.getPointCount() == 14); assertTrue(polyline.getPathCount() == 5); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); wktString = exporterWKT.execute(0, polyline, null); - assertTrue(wktString - .equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); + assertTrue(wktString.equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } // Test Import LineString wktString = "Linestring Z(10 10 5, 10 20 5, 20 20 5, 20 10 5)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline.getPointCount() == 4); - wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, - polyline, null); - assertTrue(wktString - .equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); + wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, polyline, null); + assertTrue(wktString.equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = exporterWKT.execute(0, polyline, null); - assertTrue(wktString - .equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); + assertTrue(wktString.equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1234,10 +1086,8 @@ public static void testImportExportWktMultiLineString() { @Test public static void testImportExportWktMultiPoint() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); MultiPoint multipoint; String wktString; @@ -1245,10 +1095,8 @@ public static void testImportExportWktMultiPoint() { WktParser wktParser = new WktParser(); // Test Import from Multi_point - wktString = " MultiPoint ZM empty"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(multipoint != null); assertTrue(multipoint.isEmpty()); assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); @@ -1260,8 +1108,7 @@ public static void testImportExportWktMultiPoint() { while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, - multipoint, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); assertTrue(wktString.equals("POINT ZM EMPTY")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { @@ -1271,10 +1118,8 @@ public static void testImportExportWktMultiPoint() { multipoint.add(118.15114354234563, 33.82234433423462345); multipoint.add(88, 88); - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, - multipoint, null); - assertTrue(wktString - .equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1290,8 +1135,7 @@ public static void testImportExportWktMultiPoint() { } wktString = "Multipoint zm (empty, empty, (10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), empty, (10 10 1 33), (12 12 1 33), empty, (60 60 7 33), (60 90.1 7 33), (90 90 7 33), empty, (70 70 7 33), (70 80 7 33), (80 80 7 33), empty)"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(multipoint != null); multipoint.queryEnvelope2D(envelope); // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && @@ -1301,8 +1145,7 @@ public static void testImportExportWktMultiPoint() { assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); wktString = "Multipoint zm (10 88 88 33, 10 20 5 33, 20 20 5 33, 20 10 5 33, 12 12 3 33, 10 10 1 33, 12 12 1 33, 60 60 7 33, 60 90.1 7 33, 90 90 7 33, 70 70 7 33, 70 80 7 33, 80 80 7 33)"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(multipoint != null); // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); @@ -1310,20 +1153,16 @@ public static void testImportExportWktMultiPoint() { assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, - multipoint, null); - assertTrue(wktString - .equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = "Multipoint zm (empty, empty, (10 10 5 33))"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, - multipoint, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); assertTrue(wktString.equals("POINT ZM (10 10 5 33)")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { @@ -1332,20 +1171,16 @@ public static void testImportExportWktMultiPoint() { @Test public static void testImportExportWktPoint() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); Point point; String wktString; WktParser wktParser = new WktParser(); // Test Import from Point - wktString = "Point ZM empty"; - point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(point != null); assertTrue(point.isEmpty()); assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); @@ -1357,16 +1192,14 @@ public static void testImportExportWktPoint() { while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, - point, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, point, null); assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = "Point zm (30.1 10.6 5.1 33.1)"; - point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(point != null); assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); @@ -1380,34 +1213,30 @@ public static void testImportExportWktPoint() { assertTrue(z == 5.1); assertTrue(m == 33.1); - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, - point, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, point, null); assertTrue(wktString.equals("POINT ZM (30.1 10.6 5.1 33.1)")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint - | WktExportFlags.wktExportPrecision15, point, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint | WktExportFlags.wktExportPrecision15, point, null); assertTrue(wktString.equals("MULTIPOINT ZM ((30.1 10.6 5.1 33.1))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } } + @Deprecated @Test - public static void testImportGeoJsonGeometryCollection() - throws JSONException { - OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonGeometryCollection() throws JSONException { + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); String geoJsonString; Envelope2D envelope = new Envelope2D(); WktParser wktParser = new WktParser(); geoJsonString = "{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Point\", \"coordinates\": [0,0]}, {\"type\" : \"GeometryCollection\" , \"geometries\" : [ {\"type\" : \"Point\", \"coordinates\" : [0, 0]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]} ,{ \"type\" : \"Point\", \"coordinates\" : [2, 2]}, {\"type\" : \"LineString\", \"coordinates\" : []}]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"Point\" , \"coordinates\" : [2, 2]} ] }"; - OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures - .get(0); + OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures.get(0); assertTrue(structure.m_type == 7); assertTrue(structure.m_structures.get(0).m_type == 1); @@ -1423,288 +1252,485 @@ public static void testImportGeoJsonGeometryCollection() } @Test - public static void testImportGeoJsonMultiPolygon() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonMultiPolygon() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; Polygon polygon; + SpatialReference spatial_reference; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from MultiPolygon - - geoJsonString = "{\"type\": \"Multipolygon\", \"coordinates\": []}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": []}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); - polygon = (Polygon) (GeometryEngine.geometryFromGeoJson(geoJsonString, - 0, Geometry.Type.Unknown).getGeometry()); + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\": {\"type\": \"name\", \"some\": \"stuff\", \"properties\": {\"some\" : \"stuff\", \"name\": \"urn:ogc:def:crs:OGC:1.3:CRS84\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); - assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = "{\"coordinates\" : null, \"crs\": null, \"type\": \"MultiPolygon\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference == null); - geoJsonString = "{\"type\": \"Multipolygon\", \"coordinates\": [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []]}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\" : [[], [], [[[]]]], \"crsURN\": \"urn:ogc:def:crs:OGC:1.3:CRS27\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference != null); + assertTrue(spatial_reference.getLatestID() == 4267); + + geoJsonString = "{\"coordinates\" : [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []], \"crs\": {\"type\": \"link\", \"properties\": {\"href\": \"http://spatialreference.org/ref/sr-org/6928/ogcwkt/\"}}, \"type\": \"MultiPolygon\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); - // assertTrue(polygon.calculate_area_2D() > 0.0); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(spatial_reference.getLatestID() == 3857); - // double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, - // 0, 0); - // assertTrue(z == 5); - - // Test import Polygon - geoJsonString = "{\"type\": \"POLYGON\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + JSONObject jsonObject = new JSONObject(geoJsonString); + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, jsonObject, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - - geoJsonString = "{\"type\": \"MULTIPOLYGON\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + assertTrue(spatial_reference.getLatestID() == 3857); + + // Test Export to GeoJSON MultiPolygon + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportSkipCRS, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]]}")); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring + // i // clockwise + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); assertTrue(polygon != null); assertTrue(polygon.getPointCount() == 4); assertTrue(polygon.getPathCount() == 1); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polygon.calculateArea2D() > 0); + + // Test import Polygon + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"Polygon\",\"coordinates\":[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]],[[60,60,7],[60,90,7],[90,90,7],[60,60,7]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}")); + + Envelope env = new Envelope(); + env.addAttribute(VertexDescription.Semantics.Z); + polygon.queryEnvelope(env); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"esriwkt\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + String wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + + // AGOL exports wkt like this... + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + + boolean exceptionThrownNoWKT = false; + + try { + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, + spatial_reference, polygon); + } catch (Exception e) { + exceptionThrownNoWKT = true; + } + + assertTrue(exceptionThrownNoWKT); } @Test - public static void testImportGeoJsonMultiLineString() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - + public static void testImportGeoJsonMultiLineString() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; Polyline polyline; + SpatialReference spatial_reference; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from MultiLineString - - geoJsonString = "{\"type\": \"MultiLineString\", \"coordinates\": []}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\":\"MultiLineString\",\"coordinates\":[], \"crs\" : {\"type\" : \"URL\", \"properties\" : {\"url\" : \"http://www.opengis.net/def/crs/EPSG/0/3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polyline != null); + assertTrue(spatial_reference != null); assertTrue(polyline.isEmpty()); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 3857); - geoJsonString = "{\"type\": \"MultiLineString\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.spatialreference.org/ref/epsg/4309/\"}}, \"type\":\"MultiLineString\",\"coordinates\":[[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polyline.getPointCount() == 14); assertTrue(polyline.getPathCount() == 5); - // assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(spatial_reference.getLatestID() == 4309); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"MultiLineString\",\"coordinates\":[[[10,10,5],[10,20,5],[20,88,5],[20,10,5]],[[12,88,3],[12,88,3]],[[10,10,1],[12,12,1]],[[88,60,7],[60,90,7],[90,90,7]],[[70,70,7],[70,80,7],[80,80,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4309\"}}}")); // Test Import LineString - geoJsonString = "{\"type\": \"Linestring\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline.getPointCount() == 4); - // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - geoJsonString = "{\"type\": \"Linestring\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline.getPointCount() == 4); - // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\":\"LineString\",\"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [], [20, 10, 5]],\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.opengis.net/def/crs/EPSG/0/3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) (map_geometry.getGeometry()); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polyline.getPointCount() == 4); + assertTrue(spatial_reference.getLatestID() == 3857); + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = exporterGeoJson.execute(0, null, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":null}")); } @Test - public static void testImportGeoJsonMultiPoint() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - + public static void testImportGeoJsonMultiPoint() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; MultiPoint multipoint; + SpatialReference spatial_reference; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from Multi_point - geoJsonString = "{\"type\": \"MultiPoint\", \"coordinates\": []}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[]}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + multipoint = (MultiPoint) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(multipoint != null); assertTrue(multipoint.isEmpty()); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = exporterGeoJson.execute(0, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); multipoint = new MultiPoint(); - multipoint.add(118.15114354234563, 33.82234433423462345); + multipoint.add(118.15, 2); multipoint.add(88, 88); - multipoint = new MultiPoint(); + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision16, SpatialReference.create(4269), multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[118.15,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4269\"}}}")); + + multipoint.setEmpty(); multipoint.add(88, 2); multipoint.add(88, 88); - geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []]}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = exporterGeoJson.execute(0, SpatialReference.create(102100), multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[88,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []],\"crs\":{\"type\":\"OGC\",\"properties\":{\"urn\":\"urn:ogc:def:crs:OGC:1.3:CRS83\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + multipoint = (MultiPoint) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(multipoint != null); - multipoint.queryEnvelope2D(envelope); - // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && - // envelope.ymin == 10 && Math.abs(envelope.ymax - 90.1) <= 0.001); assertTrue(multipoint.getPointCount() == 13); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4269); - geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; + multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); assertTrue(multipoint != null); - // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && - // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); assertTrue(multipoint.getPointCount() == 13); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[], [], [10, 10, 5, 33]]}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,88,88,33],[10,20,5,33],[20,20,5,33],[20,10,5,33],[12,12,3,33],[10,10,1,33],[12,12,1,33],[60,60,7,33],[60,90.1,7,33],[90,90,7,33],[70,70,7,33],[70,80,7,33],[80,80,7,33]],\"crs\":null}")); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 10, 5, 33]]}"; + multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); + + geoJsonString = exporterGeoJson.execute(0, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,10,5,33]],\"crs\":null}")); } @Test - public static void testImportGeoJsonPolygon() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonPolygon() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); Polygon polygon; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from Polygon - geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": []}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]]}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polygon.getPointCount() == 8); assertTrue(polygon.getPathCount() == 3); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - geoJsonString = "{\"type\": \"polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; - Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; + Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polygon2 != null); } @Test - public static void testImportGeoJsonLineString() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonLineString() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); Polyline polyline; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from LineString - geoJsonString = "{\"type\": \"LineString\", \"coordinates\": []}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline != null); assertTrue(polyline.isEmpty()); assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polyline.getPointCount() == 4); assertTrue(polyline.getPathCount() == 1); - // assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); } @Test - public static void testImportGeoJsonPoint() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - + public static void testImportGeoJsonPoint() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; + SpatialReference spatial_reference; Point point; String geoJsonString; // Test Import from Point + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + point = (Point) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(spatial_reference.getLatestID() == 3857); - geoJsonString = "{\"type\": \"Point\", \"coordinates\": []}"; - point = (Point) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); assertTrue(point != null); assertTrue(point.isEmpty()); - assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); - geoJsonString = "{\"type\": \"Point\", \"coordinates\": [30.1, 10.6, 5.1, 33.1]}"; - point = (Point) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); + geoJsonString = exporterGeoJson.execute(0, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[],\"crs\":null}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); + + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"urn:ogc:def:crs:ESRI::54051\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + point = (Point) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(point != null); - // assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 54051); double x = point.getX(); double y = point.getY(); - // double z = point.getZ(); - // double m = point.getM(); + double z = point.getZ(); + double m = point.getM(); assertTrue(x == 30.1); assertTrue(y == 10.6); - // assertTrue(z == 5.1); - // assertTrue(m == 33.1); + assertTrue(z == 5.1); + assertTrue(m == 33.1); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, spatial_reference, point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:54051\"}}}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, SpatialReference.create(4287), point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4287\"}}}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry | GeoJsonExportFlags.geoJsonExportPrecision15, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[30.1,10.6,5.1,33.1]],\"crs\":null}")); } @Test - public static void testImportGeoJsonSpatialReference() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportExportGeoJsonMalformed() { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + + String geoJsonString; + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],2,4]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"LineString\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[[]]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[{}]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,[],33.1],\"crs\":\"EPSG:3857\"}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + } + + @Test + public static void testImportGeoJsonSpatialReference() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); String geoJsonString4326; String geoJsonString3857; // Test Import from Point - geoJsonString4326 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:4326\"}"; geoJsonString3857 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:3857\"}"; - MapGeometry mapGeometry4326 = importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString4326, null); - MapGeometry mapGeometry3857 = importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString3857, null); + MapGeometry mapGeometry4326 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString4326, null); + MapGeometry mapGeometry3857 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString3857, null); assertTrue(mapGeometry4326.equals(mapGeometry3857) == false); - assertTrue(mapGeometry4326.getGeometry().equals( - mapGeometry3857.getGeometry())); + assertTrue(mapGeometry4326.getGeometry().equals(mapGeometry3857.getGeometry())); } public static Polygon makePolygon() { diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index ab7dd9f0..8e3f7fcc 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -1,8 +1,11 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; +import com.esri.core.geometry.PolygonUtils.PiPResult; + //import java.util.Random; public class TestIntersection extends TestCase { @@ -1038,4 +1041,47 @@ public void testIntersectionIssue2() { boolean eq = OperatorEquals.local().execute(g, polyline, sr, null); assertTrue(eq); } + + /* + Point2D uniqueIntersectionPointOfNonDisjointGeometries(Geometry g1, Geometry g2, SpatialReference sr) { + Geometry g1Test = g1; + boolean g1Polygon = g1.getType() == Geometry.Type.Polygon; + boolean g2Polygon = g2.getType() == Geometry.Type.Polygon; + + if (g1Polygon || g2Polygon) + { + if (g1Polygon) { + Point2D p = getFirstPoint(g2); + if (PolygonUtils.isPointInPolygon2D((Polygon)g1, p, 0) != PiPResult.PiPOutside) + return p; + } + if (g2Polygon) { + Point2D p = getFirstPoint(g1); + if (PolygonUtils.isPointInPolygon2D((Polygon)g2, p, 0) != PiPResult.PiPOutside) + return p; + } + } + + if (g1Polygon) + { + Polyline polyline = new Polyline(); + polyline.add((MultiPath)g1, false); + g1Test = polyline; + } + Geometry g2Test = g2; + if (g2Polygon) + { + Polyline polyline = new Polyline(); + polyline.add((MultiPath)g2, false); + g2Test = polyline; + } + + GeometryCursor gc = OperatorIntersection.local().execute(new SimpleGeometryCursor(g1Test), new SimpleGeometryCursor(g2Test), sr, null, 3); + for (Geometry res = gc.next(); res != null; res = gc.next()) { + return getFirstPoint(res); + } + + throw new GeometryException("internal error"); + }*/ + } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index dcca0c3a..5b2ef6b3 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -14,7 +14,6 @@ import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import org.codehaus.jackson.JsonParseException; -import org.json.JSONException; import org.junit.Test; import java.io.IOException; @@ -40,6 +39,8 @@ public void testPoint() { assertTrue(p.Y() == 2); assertTrue(g.equals(OGCGeometry.fromText("POINT(1 2)"))); assertTrue(!g.equals(OGCGeometry.fromText("POINT(1 3)"))); + assertTrue(g.equals((Object)OGCGeometry.fromText("POINT(1 2)"))); + assertTrue(!g.equals((Object)OGCGeometry.fromText("POINT(1 3)"))); OGCGeometry buf = g.buffer(10); assertTrue(buf.geometryType().equals("Polygon")); OGCPolygon poly = (OGCPolygon) buf.envelope(); @@ -48,7 +49,7 @@ public void testPoint() { } @Test - public void testPolygon() { + public void testPolygon() throws Exception { OGCGeometry g = OGCGeometry .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); assertTrue(g.geometryType().equals("Polygon")); @@ -64,14 +65,26 @@ public void testPolygon() { b = lsi.equals(OGCGeometry .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(b); + b = lsi.equals((Object)OGCGeometry + .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(!lsi.equals(ls)); OGCMultiCurve boundary = p.boundary(); String s = boundary.asText(); assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); + + { + OGCGeometry g2 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}").intersects(g2); + OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}").intersects(g2); + + OGCGeometry g3 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + boolean bb = g2.equals((Object)g3); + assertTrue(bb); + } } @Test - public void testGeometryCollection() throws JSONException { + public void testGeometryCollection() throws Exception { OGCGeometry g = OGCGeometry .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); assertTrue(g.geometryType().equals("GeometryCollection")); @@ -139,6 +152,10 @@ public void testGeometryCollection() throws JSONException { wktString = g.asText(); assertTrue(wktString .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); + + assertTrue(g.equals((Object)OGCGeometry.fromText(wktString))); + + assertTrue(g.hashCode() == OGCGeometry.fromText(wktString).hashCode()); } @@ -768,7 +785,7 @@ public void testIsectTria1() { } @Test - public void testIsectTriaJson1() throws JsonParseException, IOException { + public void testIsectTriaJson1() throws Exception { String json1 = "{\"rings\":[[[1, 0], [3, 0], [1, 2], [1, 0]]], \"spatialReference\":{\"wkid\":4326}}"; String json2 = "{\"rings\":[[[0, 1], [2, 1], [0, 3], [0, 1]]], \"spatialReference\":{\"wkid\":4326}}"; OGCGeometry g0 = OGCGeometry.fromJson(json1); @@ -868,7 +885,7 @@ public void testMultiPolygonArea() { } @Test - public void testPolylineSimplifyIssueGithub52() throws JsonParseException, IOException { + public void testPolylineSimplifyIssueGithub52() throws Exception { String json = "{\"paths\":[[[2,0],[4,3],[5,1],[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"; { OGCGeometry g = OGCGeometry.fromJson(json); diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 8901df4e..e34a14b7 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1,7 +1,10 @@ package com.esri.core.geometry; +import java.io.IOException; + import junit.framework.TestCase; +import org.json.JSONException; import org.junit.Test; import com.esri.core.geometry.ogc.OGCGeometry; @@ -1199,5 +1202,104 @@ public void testReplaceNaNs() { assertTrue(b); } - } + } + + @Test + public void testPolygon2PolygonFails() throws IOException, JSONException { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(birmingham()); + + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) factory + .getOperator(Operator.Type.ImportFromGeoJson); + MapGeometry mapGeometry = importer.execute( + GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon, result, null); + Polygon polygon = (Polygon) mapGeometry.getGeometry(); + assertEquals(birmingham(), polygon); + } + + @Test + public void testPolygon2PolygonFails2() throws JSONException { + String birminghamGeojson = GeometryEngine + .geometryToGeoJson(birmingham()); + MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon, birmingham()); + } + + @Test + public void testPolygon2PolygonWorks() throws JSONException { + String birminghamGeojson = GeometryEngine + .geometryToGeoJson(birmingham()); + MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon.toString(), birmingham().toString()); + } + + @Test + public void testPolygon2Polygon2Works() throws JSONException, IOException { + String birminghamJson = GeometryEngine.geometryToJson(4326, + birmingham()); + MapGeometry returnedGeometry = GeometryEngine + .jsonToGeometry(birminghamJson); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon, birmingham()); + String s = polygon.toString(); + } + + @Test + public void testSegmentIteratorCrash() { + Polygon poly = new Polygon(); + + // clockwise => outer ring + poly.startPath(0, 0); + poly.lineTo(-0.5, 0.5); + poly.lineTo(0.5, 1); + poly.lineTo(1, 0.5); + poly.lineTo(0.5, 0); + + // hole + poly.startPath(0.5, 0.2); + poly.lineTo(0.6, 0.5); + poly.lineTo(0.2, 0.9); + poly.lineTo(-0.2, 0.5); + poly.lineTo(0.1, 0.2); + poly.lineTo(0.2, 0.3); + + // island + poly.startPath(0.1, 0.7); + poly.lineTo(0.3, 0.7); + poly.lineTo(0.3, 0.4); + poly.lineTo(0.1, 0.4); + + assertEquals(poly.getSegmentCount(), 15); + assertEquals(poly.getPathCount(), 3); + SegmentIterator segmentIterator = poly.querySegmentIterator(); + int paths = 0; + int segments = 0; + while (segmentIterator.nextPath()) { + paths++; + Segment segment; + while (segmentIterator.hasNextSegment()) { + segment = segmentIterator.nextSegment(); + segments++; + } + } + assertEquals(paths, 3); + assertEquals(segments, 15); + } + + private static Polygon birmingham() { + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-1.954245, 52.513531, -1.837357, + 52.450123), false); + poly.addEnvelope(new Envelope(0, 0, 1, 1), false); + return poly; + } } diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index 7ffdc06d..6dde9220 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -1,6 +1,12 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.Random; +import java.util.HashMap; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.Map; import junit.framework.TestCase; @@ -19,11 +25,24 @@ protected void tearDown() throws Exception { @Test public static void test1() { + + { + QuadTree quad_tree = new QuadTree(Envelope2D.construct(-10, -10, 10, 10), 8); + + QuadTree.QuadTreeIterator qt = quad_tree.getIterator(true); + assertTrue(qt.next() == -1); + + qt.resetIterator(Envelope2D.construct(0, 0, 0, 0), 0); + + assertTrue(quad_tree.getIntersectionCount(Envelope2D.construct(0, 0, 0, 0), 0, 10) == 0); + assertTrue(quad_tree.getElementCount() == 0); + } + Polyline polyline; polyline = makePolyline(); MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); - QuadTree quadtree = buildQuadTree_(polylineImpl); + QuadTree quadtree = buildQuadTree_(polylineImpl, false); Line queryline = new Line(34, 9, 66, 46); QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); @@ -37,12 +56,12 @@ public static void test1() { assertTrue(index == 6 || index == 8 || index == 14); element_handle = qtIter.next(); } - + Envelope2D envelope = new Envelope2D(34, 9, 66, 46); Polygon queryPolygon = new Polygon(); queryPolygon.addEnvelope(envelope, true); - qtIter.resetIterator(queryline, 0.0); + qtIter.resetIterator(queryline, 0.0); element_handle = qtIter.next(); while (element_handle > 0) { @@ -52,6 +71,336 @@ public static void test1() { } } + @Test + public static void testQuadTreeWithDuplicates() { + int pass_count = 10; + int figure_size = 400; + int figure_size2 = 100; + Envelope extent1 = new Envelope(); + extent1.setCoords(-100000, -100000, 100000, 100000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); + Random random = new Random(2013); + int rand_max = 32; + + Polygon poly_red = new Polygon(); + Polygon poly_blue = new Polygon(); + + int r = figure_size; + + for (int c = 0; c < pass_count; c++) { + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_blue.startPath(pt); + else + poly_blue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); + QuadTree quad_tree_blue_duplicates = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), true); + + Envelope2D e1 = quad_tree_blue.getDataExtent(); + Envelope2D e2 = quad_tree_blue_duplicates.getDataExtent(); + assertTrue(e1.equals(e2)); + assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); + + SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); + + poly_red.setEmpty(); + + r = figure_size2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_red.startPath(pt); + else + poly_red.lineTo(pt); + } + + QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); + SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); + + HashMap map1 = new HashMap(0); + + int count = 0; + int intersections_per_query = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator.resetIterator(env, 0.0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator.next()) != -1) { + count_upper++; + int index = quad_tree_blue.getElement(element_handle); + Boolean iter = (Boolean) map1.get(new Integer(index)); + if (iter == null) { + count++; + map1.put(new Integer(index), new Boolean(true)); + } + + intersections_per_query++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper); + } + } + + seg_iter_red.resetToFirstPath(); + + HashMap map2 = new HashMap(0); + QuadTree.QuadTreeIterator iterator_duplicates = quad_tree_blue_duplicates.getIterator(); + + int count_duplicates = 0; + int intersections_per_query_duplicates = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator_duplicates.resetIterator(env, 0.0); + + int count_lower = 0; + HashMap map_per_query = new HashMap(0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator_duplicates.next()) != -1) { + count_upper++; + int index = quad_tree_blue_duplicates.getElement(element_handle); + Boolean iter = (Boolean) map2.get(new Integer(index)); + if (iter == null) { + count_duplicates++; + map2.put(new Integer(index), new Boolean(true)); + } + + Boolean iter_lower = (Boolean) map_per_query.get(index); + if (iter_lower == null) { + count_lower++; + intersections_per_query_duplicates++; + map_per_query.put(new Integer(index), new Boolean(true)); + } + + int q = quad_tree_blue_duplicates.getQuad(element_handle); + assertTrue(quad_tree_blue_duplicates.getSubTreeElementCount(q) >= quad_tree_blue_duplicates.getContainedSubTreeElementCount(q)); + } + + int intersection_count = quad_tree_blue_duplicates.getIntersectionCount(env, 0.0, -1); + boolean b_has_data = quad_tree_blue_duplicates.hasData(env, 0.0); + assertTrue(b_has_data || intersection_count == 0); + assertTrue(count_lower <= intersection_count && intersection_count <= count_upper); + assertTrue(count_upper <= 4 * count_lower); + } + } + + assertTrue(count == count_duplicates); + assertTrue(intersections_per_query == intersections_per_query_duplicates); + } + } + + @Test + public static void testSortedIterator() { + int pass_count = 10; + int figure_size = 400; + int figure_size2 = 100; + Envelope extent1 = new Envelope(); + extent1.setCoords(-100000, -100000, 100000, 100000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); + + Random random = new Random(2013); + int rand_max = 32; + + Polygon poly_red = new Polygon(); + Polygon poly_blue = new Polygon(); + + int r = figure_size; + + for (int c = 0; c < pass_count; c++) { + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_blue.startPath(pt); + else + poly_blue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); + + Envelope2D e1 = quad_tree_blue.getDataExtent(); + assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); + + SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); + + poly_red.setEmpty(); + + r = figure_size2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_red.startPath(pt); + else + poly_red.lineTo(pt); + } + + QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); + SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); + + HashMap map1 = new HashMap(0); + + int count = 0; + int intersections_per_query = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator.resetIterator(env, 0.0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator.next()) != -1) { + count_upper++; + int index = quad_tree_blue.getElement(element_handle); + Boolean iter = (Boolean) map1.get(index); + if (iter == null) { + count++; + map1.put(new Integer(index), new Boolean(true)); + } + + intersections_per_query++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper); + } + } + + seg_iter_red.resetToFirstPath(); + + HashMap map2 = new HashMap(0); + QuadTree.QuadTreeIterator sorted_iterator = quad_tree_blue.getIterator(true); + + int count_sorted = 0; + int intersections_per_query_sorted = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + sorted_iterator.resetIterator(env, 0.0); + + int count_upper_sorted = 0; + int element_handle; + int last_index = -1; + while ((element_handle = sorted_iterator.next()) != -1) { + count_upper_sorted++; + int index = quad_tree_blue.getElement(element_handle); + assertTrue(last_index < index); // ensure the element handles are returned in sorted order + last_index = index; + Boolean iter = (Boolean) map2.get(index); + if (iter == null) { + count_sorted++; + map2.put(new Integer(index), new Boolean(true)); + } + + intersections_per_query_sorted++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper_sorted); + } + } + + assertTrue(count == count_sorted); + assertTrue(intersections_per_query == intersections_per_query_sorted); + } + } + + @Test + public static void test_perf_quad_tree() { + Envelope extent1 = new Envelope(); + extent1.setCoords(-1000, -1000, 1000, 1000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(1000, extent1, 0.001); + //HiResTimer timer; + for (int N = 16; N <= 1024/**1024*/; N *= 2) { + //timer.StartMeasurement(); + + Envelope2D extent = new Envelope2D(); + extent.setCoords(-1000, -1000, 1000, 1000); + HashMap data = new HashMap(0); + QuadTree qt = new QuadTree(extent, 10); + for (int i = 0; i < N; i++) { + Envelope2D env = new Envelope2D(); + Point2D center = generator1.GetRandomCoord().getXY(); + double w = 10; + env.setCoords(center, w, w); + env.intersect(extent); + if (env.isEmpty()) + continue; + + int h = qt.insert(i, env); + data.put(new Integer(h), env); + } + + int ecount = 0; + AttributeStreamOfInt32 handles = new AttributeStreamOfInt32(0); + QuadTree.QuadTreeIterator iter = qt.getIterator(); + + Iterator> pairs = data.entrySet().iterator(); + while (pairs.hasNext()) { + Map.Entry entry = pairs.next(); + iter.resetIterator((Envelope2D) entry.getValue(), 0.001); + boolean remove_self = false; + for (int h = iter.next(); h != -1; h = iter.next()) { + if (h != entry.getKey().intValue()) + handles.add(h); + else { + remove_self = true; + } + + ecount++; + } + + for (int i = 0; i < handles.size(); i++) { + qt.removeElement(handles.get(i));//remove elements that were selected. + } + + if (remove_self) + qt.removeElement(entry.getKey().intValue()); + handles.resize(0); + } + + //printf("%d %0.3f (%I64d, %f, mem %I64d)\n", N, timer.GetMilliseconds(), ecount, ecount / double(N * N), memsize); + } + } + @Test public static void test2() { MultiPoint multipoint = new MultiPoint(); @@ -81,7 +430,7 @@ public static void test2() { assertTrue(count == 10000); } - + public static Polyline makePolyline() { Polyline poly = new Polyline(); @@ -120,10 +469,10 @@ public static Polyline makePolyline() { return poly; } - static QuadTree buildQuadTree_(MultiPathImpl multipathImpl) { + static QuadTree buildQuadTree_(MultiPathImpl multipathImpl, boolean bStoreDuplicates) { Envelope2D extent = new Envelope2D(); multipathImpl.queryEnvelope2D(extent); - QuadTree quadTree = new QuadTree(extent, 8); + QuadTree quadTree = new QuadTree(extent, 8, bStoreDuplicates); int hint_index = -1; Envelope2D boundingbox = new Envelope2D(); SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index ec21049d..5c00d8ad 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -1,7 +1,10 @@ package com.esri.core.geometry; +import java.io.IOException; + import junit.framework.TestCase; +import org.codehaus.jackson.JsonParseException; import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; @@ -5481,7 +5484,7 @@ public void testCrosses_github_issue_40() { null); assertTrue(answer2); } - + @Test public void testDisjointCrash() { Polygon g1 = new Polygon(); @@ -5492,5 +5495,14 @@ public void testDisjointCrash() { OperatorDisjoint.local().accelerateGeometry(g1, SpatialReference.create(4267), GeometryAccelerationDegree.enumHot); boolean res = OperatorDisjoint.local().execute(g1, g2, SpatialReference.create(4267), null); assertTrue(!res); - } + } + + @Test + public void testDisjointFail() throws JsonParseException, IOException { + MapGeometry geometry1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"paths\":[[[3,3],[3,3]]],\"spatialReference\":{\"wkid\":4326}}"); + MapGeometry geometry2 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[2,2],[2,4],[4,4],[4,2],[2,2]]],\"spatialReference\":{\"wkid\":4326}}"); + OperatorDisjoint.local().accelerateGeometry(geometry1.getGeometry(), geometry1.getSpatialReference(), GeometryAccelerationDegree.enumMedium); + boolean res = OperatorDisjoint.local().execute(geometry1.getGeometry(), geometry2.getGeometry(), geometry1.getSpatialReference(), null); + assertTrue(!res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 289546da..4d736a8c 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -37,18 +37,17 @@ public void testSerializePoint() { } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPoint.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Point pt = new Point(10, 40); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Point serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Point pt = new Point(10, 40, 2); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Point serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -59,6 +58,17 @@ public void testSerializePoint() { } catch (Exception ex) { fail("Point serialization failure"); } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); + } catch (Exception ex) { + fail("Point serialization failure"); + } + } @Test @@ -98,22 +108,21 @@ public void testSerializePolygon() { fail("Polygon serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPolygon.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Polygon pt = new Polygon(); - // pt.startPath(10, 10); - // pt.lineTo(100, 100); - // pt.lineTo(200, 100); - // pt = (Polygon)GeometryEngine.simplify(pt, null); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Polygon serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polygon pt = new Polygon(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //pt = (Polygon)GeometryEngine.simplify(pt, null); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polygon serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -124,6 +133,15 @@ public void testSerializePolygon() { } catch (Exception ex) { fail("Polygon serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } } @Test @@ -145,21 +163,20 @@ public void testSerializePolyline() { fail("Polyline serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPolyline.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Polyline pt = new Polyline(); - // pt.startPath(10, 10); - // pt.lineTo(100, 100); - // pt.lineTo(200, 100); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Polyline serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polyline pt = new Polyline(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polyline serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -170,6 +187,15 @@ public void testSerializePolyline() { } catch (Exception ex) { fail("Polyline serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } } @Test @@ -188,18 +214,17 @@ public void testSerializeEnvelope() { fail("Envelope serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedEnvelope.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Envelope pt = new Envelope(10, 10, 400, 300); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Envelope serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Envelope pt = new Envelope(10, 10, 400, 300); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Envelope serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -210,6 +235,15 @@ public void testSerializeEnvelope() { } catch (Exception ex) { fail("Envelope serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } } @Test @@ -230,20 +264,19 @@ public void testSerializeMultiPoint() { fail("MultiPoint serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedMultiPoint.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // MultiPoint pt = new MultiPoint(); - // pt.add(10, 30); - // pt.add(120, 40); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("MultiPoint serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //MultiPoint pt = new MultiPoint(); + //pt.add(10, 30); + //pt.add(120, 40); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("MultiPoint serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -254,6 +287,15 @@ public void testSerializeMultiPoint() { } catch (Exception ex) { fail("MultiPoint serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } } @Test diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReference.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java index f0f6aacd..8d21712f 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReference.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -1,17 +1,17 @@ package com.esri.core.geometry; -import org.junit.Assert; +import junit.framework.TestCase; import org.junit.Test; -public class TestSpatialReference extends Assert { +public class TestSpatialReference extends TestCase { @Test - public void equals() { - final String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; - final String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; + public void testEquals() { + String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; + String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; - final SpatialReference a1 = SpatialReference.create(wktext1); - final SpatialReference b = SpatialReference.create(wktext2); - final SpatialReference a2 = SpatialReference.create(wktext1); + SpatialReference a1 = SpatialReference.create(wktext1); + SpatialReference b = SpatialReference.create(wktext2); + SpatialReference a2 = SpatialReference.create(wktext1); assertTrue(a1.equals(a1)); assertTrue(b.equals(b)); @@ -22,3 +22,4 @@ public void equals() { assertFalse(b.equals(a1)); } } + diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index d953ae95..cd249233 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -19,6 +19,7 @@ public void test() { assertTrue(Math.abs(tol84 - 1e-8) < 1e-8 * 1e-8); } + @Test public void test_80() { SpatialReference sr = SpatialReference.create(3857); diff --git a/src/test/resources/com/esri/core/geometry/savedEnvelope1.txt b/src/test/resources/com/esri/core/geometry/savedEnvelope1.txt new file mode 100644 index 0000000000000000000000000000000000000000..5f6ee18b35dd78a3e7b99ced5651419236b26633 GIT binary patch literal 147 zcmZ4UmVvdnh(SI%KUXicxF}OEIlm}XFFiFsH?^dwQqMK7EVwAAs)zvs7?~KDJQ;*i zQj3#|G7CyF^YffCOMDZHv!fZ<6H7{pGLwo+7?`46Dhhz=8B2>mY`bMWBCC_5VbQ)Hh?de9goI?Sb(Ihd!@~yD8DGIP#};cx03u! Q!VW4B!yH_oG=yRR03RzM^8f$< literal 0 HcmV?d00001 diff --git a/src/test/resources/com/esri/core/geometry/savedPolygon1.txt b/src/test/resources/com/esri/core/geometry/savedPolygon1.txt new file mode 100644 index 0000000000000000000000000000000000000000..1fd9c10556e0e3d2635c76546f9792ce3cf02af3 GIT binary patch literal 317 zcmZ4UmVvdnh`}H^KUXicxF}OEIlm}XFFiFsH?^dwQqMg#FSRH$*&WIc267T}GOJRH z7$AU=iGkIVfd{5Oq_QB@lYy%^Gq)fo)h#D6-Gza(BtIv$C^0WNHJX7FWJ7R9VnJ#N z15>n9Gy_L|dNN2^F^C^kQNX~!b^>THqwW4ku=%A$Aa!d#i~nHb2zkK7zyMOhP{0bY z97w7-K$tK(6UvWtU<69AKqR3gObv{VgsX)pKhhJ4{G&ViGA8vUFV9ljO{8ykDCr;U|*#j$y*`(9?WSX5^up z^E69nkwozvb|Dgy(VkHVTQ&H*D5C;qI1AAcF;iHs{Q8&yZTD4A^Bo7Nxau)@wmZSq u1Qdd8OXnC(zpGZ>_|?>?gFmMz&1hZySd{|*+fVI!c^*#sm(5Mr8h8V&^+ZAd literal 0 HcmV?d00001 From 04e92ca0de02a6b8ad6452bd48ebbe7361eea272 Mon Sep 17 00:00:00 2001 From: Kevin Krumwiede Date: Mon, 22 Aug 2016 00:09:28 -0700 Subject: [PATCH 032/116] Disable doclint when building with Java 1.8+. --- pom.xml | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 7a72602c..0933ae01 100755 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,9 @@ - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo @@ -39,7 +39,7 @@ - + scm:git:git@github.com:Esri/geometry-api-java.git scm:git:git@github.com:Esri/geometry-api-java.git @@ -49,12 +49,12 @@ release-sign-artifacts - - - performRelease - true - - + + + performRelease + true + + @@ -73,15 +73,24 @@ + + java8-disable-doclint + + [1.8,) + + + -Xdoclint:none + + - + ossrh https://oss.sonatype.org/content/repositories/snapshots - + UTF-8 @@ -138,12 +147,12 @@ - maven-compiler-plugin - ${compiler.plugin.version} - - ${java.source.version} - ${java.target.version} - + maven-compiler-plugin + ${compiler.plugin.version} + + ${java.source.version} + ${java.target.version} + org.apache.maven.plugins @@ -172,6 +181,9 @@ jar + + ${javadoc.doclint.param} + From 9e8a4e4244fc3779a47c44072af5875f1006634e Mon Sep 17 00:00:00 2001 From: Kevin Krumwiede Date: Mon, 22 Aug 2016 00:23:35 -0700 Subject: [PATCH 033/116] Undo IDEA's formatting corrections to reduce noise in pull request diff. --- pom.xml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 0933ae01..3bdf4a44 100755 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,9 @@ - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo @@ -49,12 +49,12 @@ release-sign-artifacts - - - performRelease - true - - + + + performRelease + true + + @@ -147,12 +147,12 @@ - maven-compiler-plugin - ${compiler.plugin.version} - - ${java.source.version} - ${java.target.version} - + maven-compiler-plugin + ${compiler.plugin.version} + + ${java.source.version} + ${java.target.version} + org.apache.maven.plugins From 6c9eead489c5ec5812e591eb57630bbeac0aee78 Mon Sep 17 00:00:00 2001 From: Kevin Krumwiede Date: Mon, 22 Aug 2016 12:07:01 -0700 Subject: [PATCH 034/116] Set Buffer byte order before reading shape type. --- .../OperatorImportFromESRIShapeCursor.java | 212 +++++++++--------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java index 2186b4e4..2db25375 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java @@ -63,116 +63,116 @@ public int getGeometryID() { } private Geometry importFromESRIShape(ByteBuffer shapeBuffer) { - // read type - int shapetype = shapeBuffer.getInt(0); - - // Extract general type and modifiers - int generaltype; - int modifiers; - switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { - // Polygon - case ShapeType.ShapePolygon: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = 0; - break; - case ShapeType.ShapePolygonZM: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolygonM: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolygonZ: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPolygon: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Polyline - case ShapeType.ShapePolyline: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = 0; - break; - case ShapeType.ShapePolylineZM: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolylineM: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolylineZ: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPolyline: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // MultiPoint - case ShapeType.ShapeMultiPoint: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = 0; - break; - case ShapeType.ShapeMultiPointZM: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = (int) ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapeMultiPointM: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapeMultiPointZ: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralMultiPoint: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Point - case ShapeType.ShapePoint: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = 0; - break; - case ShapeType.ShapePointZM: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePointM: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePointZ: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPoint: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Null Geometry - case ShapeType.ShapeNull: - return null; - - default: - throw new GeometryException("invalid shape type"); - } - ByteOrder initialOrder = shapeBuffer.order(); shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); try { + // read type + int shapetype = shapeBuffer.getInt(0); + + // Extract general type and modifiers + int generaltype; + int modifiers; + switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { + // Polygon + case ShapeType.ShapePolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = 0; + break; + case ShapeType.ShapePolygonZM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonZ: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Polyline + case ShapeType.ShapePolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = 0; + break; + case ShapeType.ShapePolylineZM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineZ: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // MultiPoint + case ShapeType.ShapeMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = 0; + break; + case ShapeType.ShapeMultiPointZM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = (int) ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointZ: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Point + case ShapeType.ShapePoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = 0; + break; + case ShapeType.ShapePointZM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointZ: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Null Geometry + case ShapeType.ShapeNull: + return null; + + default: + throw new GeometryException("invalid shape type"); + } + switch (generaltype) { case ShapeType.ShapeGeneralPolygon: if (m_type != Geometry.GeometryType.Polygon From cb33db43d3e9190a8fcefeab6137ae8014581343 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 23 Sep 2016 12:46:08 -0700 Subject: [PATCH 035/116] don't use JSONObject.getNames --- .../core/geometry/JSONObjectEnumerator.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java index 7b8f2e8b..fa1a278a 100644 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java @@ -23,18 +23,17 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; import org.json.JSONObject; +import java.util.Iterator; + final class JSONObjectEnumerator { private JSONObject m_jsonObject; private boolean m_bStarted; private int m_currentIndex; - private String[] m_keys; + private Iterator m_keys_iter; + private String m_current_key; JSONObjectEnumerator(JSONObject jsonObject) { m_bStarted = false; @@ -51,7 +50,7 @@ String getCurrentKey() { throw new GeometryException("invalid call"); } - return m_keys[m_currentIndex]; + return m_current_key; } Object getCurrentObject() { @@ -63,15 +62,23 @@ Object getCurrentObject() { throw new GeometryException("invalid call"); } - return m_jsonObject.opt(m_keys[m_currentIndex]); + return m_jsonObject.opt(m_current_key); } boolean next() { if (!m_bStarted) { m_currentIndex = 0; - m_keys = JSONObject.getNames(m_jsonObject); + m_keys_iter = m_jsonObject.keys(); m_bStarted = true; + if (m_keys_iter.hasNext()) { + m_current_key = (String)m_keys_iter.next(); + } + } else if (m_currentIndex != m_jsonObject.length()) { + if (m_keys_iter.hasNext()) { + m_current_key = (String)m_keys_iter.next(); + } + m_currentIndex++; } From ff44877f8ba5a2cbb10f50bb3e9674269675c1c0 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 27 Sep 2016 11:17:34 -0700 Subject: [PATCH 036/116] resore exceptions in the interface --- src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index c0a93ce2..2e597e7e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -496,7 +496,7 @@ public static OGCGeometry fromEsriShape(ByteBuffer buffer) { } public static OGCGeometry fromJson(String string) - throws Exception { + throws JsonParseException, IOException { JsonFactory factory = new JsonFactory(); JsonParser jsonParserPt = factory.createJsonParser(string); jsonParserPt.nextToken(); @@ -506,7 +506,7 @@ public static OGCGeometry fromJson(String string) } public static OGCGeometry fromGeoJson(String string) - throws Exception { + throws JsonParseException, IOException { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); From 0b58f1df25be5851173932a386f6d7b648b64220 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 27 Sep 2016 11:57:56 -0700 Subject: [PATCH 037/116] cleanup --- .../core/geometry/JSONObjectEnumerator.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java index fa1a278a..2b4e25b4 100644 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java @@ -30,23 +30,17 @@ final class JSONObjectEnumerator { private JSONObject m_jsonObject; - private boolean m_bStarted; - private int m_currentIndex; + private int m_troolean; private Iterator m_keys_iter; private String m_current_key; JSONObjectEnumerator(JSONObject jsonObject) { - m_bStarted = false; - m_currentIndex = -1; + m_troolean = 0; m_jsonObject = jsonObject; } String getCurrentKey() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { + if (m_troolean != 1) { throw new GeometryException("invalid call"); } @@ -54,11 +48,7 @@ String getCurrentKey() { } Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { + if (m_troolean != 1) { throw new GeometryException("invalid call"); } @@ -66,22 +56,25 @@ Object getCurrentObject() { } boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_keys_iter = m_jsonObject.keys(); - m_bStarted = true; - if (m_keys_iter.hasNext()) { - m_current_key = (String)m_keys_iter.next(); + if (m_troolean == 0) { + if (m_jsonObject.length() > 0) { + m_keys_iter = m_jsonObject.keys(); + m_troolean = 1;//started } - - } else if (m_currentIndex != m_jsonObject.length()) { + else { + m_troolean = -1;//stopped + } + } + + if (m_troolean == 1) {//still exploring if (m_keys_iter.hasNext()) { m_current_key = (String)m_keys_iter.next(); } - - m_currentIndex++; + else { + m_troolean = -1; //done + } } - return m_currentIndex != m_jsonObject.length(); + return m_troolean == 1; } } From 49e9f35c8604f96f3c9a74800434fcbe2666dbbd Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 28 Sep 2016 10:18:39 -0700 Subject: [PATCH 038/116] fix fromGeoJson interface --- src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 2e597e7e..04b2df77 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -12,7 +12,6 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Envelope1D; -import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryCursorAppend; @@ -30,7 +29,6 @@ import com.esri.core.geometry.OperatorFactoryLocal; import com.esri.core.geometry.OperatorImportFromESRIShape; import com.esri.core.geometry.OperatorImportFromGeoJson; -import com.esri.core.geometry.OperatorImportFromJson; import com.esri.core.geometry.OperatorImportFromWkb; import com.esri.core.geometry.OperatorImportFromWkt; import com.esri.core.geometry.OperatorIntersection; @@ -506,7 +504,7 @@ public static OGCGeometry fromJson(String string) } public static OGCGeometry fromGeoJson(String string) - throws JsonParseException, IOException { + throws JSONException { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); From e758fc62a8700dbdc5077c0d8a6268371b2079e3 Mon Sep 17 00:00:00 2001 From: will Date: Wed, 9 Nov 2016 11:39:42 +0100 Subject: [PATCH 039/116] test case for polyline buffer which throws npe --- src/test/java/com/esri/core/geometry/TestBuffer.java | 11 +++++++++++ 1 file changed, 11 insertions(+) mode change 100644 => 100755 src/test/java/com/esri/core/geometry/TestBuffer.java diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java old mode 100644 new mode 100755 index 38902f83..f381234b --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -266,6 +266,17 @@ public void testBufferPolyline() { assertTrue(Math.abs(pointCount - 208.0) < 10); assertTrue(simplify.isSimpleAsFeature(result, sr, null)); } + + { + inputGeom = new Polyline(); + inputGeom.startPath(1.762614,0.607368); + inputGeom.lineTo(1.762414,0.606655); + inputGeom.lineTo(1.763006,0.607034); + inputGeom.lineTo(1.762548,0.607135); + + Geometry result = buffer.execute(inputGeom, sr, 0.005, null); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } } @Test From a9435477e5dc84404f68d6b4c5e5ef48c9fc7c41 Mon Sep 17 00:00:00 2001 From: will Date: Wed, 9 Nov 2016 17:47:37 +0100 Subject: [PATCH 040/116] fixed npe caused by buffer of polyline --- src/main/java/com/esri/core/geometry/Bufferer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 src/main/java/com/esri/core/geometry/Bufferer.java diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java old mode 100644 new mode 100755 index b7008559..7578a76c --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -805,7 +805,7 @@ private Geometry bufferPoint_() { private Geometry bufferPoint_(Point point) { assert (m_distance > 0); - Polygon resultPolygon = new Polygon(m_geometry.getDescription()); + Polygon resultPolygon = new Polygon(point.getDescription()); addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); return setStrongSimple_(resultPolygon); } From dcbabdc71e20dfb1b3dd19374fb49e076ed61658 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 7 Apr 2017 15:09:21 -0700 Subject: [PATCH 041/116] README: remove tags; update copyright to 2017 --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 535a2da9..d6d243c1 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2016 Esri +Copyright 2013-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -68,7 +68,3 @@ See the License for the specific language governing permissions and limitations under the License. A copy of the license is available in the repository's [license.txt](https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. - -[](Esri Tags: ArcGIS, Java, Geometry, Relationship, Analysis, JSON, WKT, Shape) -[](Esri Language: Java) - From 9bedde397f2f61675bc687b95875893aa7cd7f2f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 5 Jul 2017 17:09:07 -0700 Subject: [PATCH 042/116] removed org.json dependency --- DepFiles/public/jackson-core-2.6.2.jar | Bin 0 -> 258824 bytes DepFiles/public/jackson-core-asl-1.9.11.jar | Bin 232131 -> 0 bytes DepFiles/public/java-json.jar | Bin 42100 -> 0 bytes DepFiles/unittest/junit-4.12.jar | Bin 0 -> 314932 bytes DepFiles/unittest/junit-4.8.2.jar | Bin 237344 -> 0 bytes build.xml | 6 +- pom.xml | 15 +- .../esri/core/geometry/GeometryEngine.java | 134 +- .../core/geometry/JSONArrayEnumerator.java | 67 - .../core/geometry/JSONObjectEnumerator.java | 80 -- .../com/esri/core/geometry/JSONUtils.java | 21 +- .../core/geometry/JsonGeometryException.java | 18 +- .../esri/core/geometry/JsonParserReader.java | 147 ++- .../com/esri/core/geometry/JsonReader.java | 59 +- ...arserCursor.java => JsonReaderCursor.java} | 28 +- .../esri/core/geometry/JsonValueReader.java | 233 ---- .../com/esri/core/geometry/MultiPathImpl.java | 44 +- .../java/com/esri/core/geometry/Operator.java | 3 +- .../core/geometry/OperatorFactoryLocal.java | 17 +- .../geometry/OperatorImportFromGeoJson.java | 13 +- .../OperatorImportFromGeoJsonLocal.java | 1143 +++++++---------- .../core/geometry/OperatorImportFromJson.java | 26 +- .../OperatorImportFromJsonCursor.java | 55 +- .../geometry/OperatorImportFromJsonLocal.java | 38 +- ...ursor.java => SimpleJsonReaderCursor.java} | 16 +- .../esri/core/geometry/SpatialReference.java | 23 +- .../esri/core/geometry/ogc/OGCGeometry.java | 48 +- .../com/esri/core/geometry/GeometryUtils.java | 9 +- .../esri/core/geometry/TestCommonMethods.java | 11 +- .../com/esri/core/geometry/TestContains.java | 16 +- .../com/esri/core/geometry/TestDistance.java | 14 +- .../esri/core/geometry/TestGeomToGeoJson.java | 25 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 43 +- .../esri/core/geometry/TestImportExport.java | 7 +- .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 11 +- .../esri/core/geometry/TestJsonParser.java | 12 +- .../java/com/esri/core/geometry/TestOGC.java | 12 +- .../com/esri/core/geometry/TestPolygon.java | 13 +- .../com/esri/core/geometry/TestRelation.java | 3 +- .../com/esri/core/geometry/TestSimplify.java | 26 +- .../esri/core/geometry/TestWKBSupport.java | 58 +- 41 files changed, 902 insertions(+), 1592 deletions(-) create mode 100644 DepFiles/public/jackson-core-2.6.2.jar delete mode 100644 DepFiles/public/jackson-core-asl-1.9.11.jar delete mode 100644 DepFiles/public/java-json.jar create mode 100644 DepFiles/unittest/junit-4.12.jar delete mode 100644 DepFiles/unittest/junit-4.8.2.jar delete mode 100644 src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java delete mode 100644 src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java rename src/main/java/com/esri/core/geometry/{JsonParserCursor.java => JsonReaderCursor.java} (68%) delete mode 100644 src/main/java/com/esri/core/geometry/JsonValueReader.java rename src/main/java/com/esri/core/geometry/{SimpleJsonParserCursor.java => SimpleJsonReaderCursor.java} (78%) diff --git a/DepFiles/public/jackson-core-2.6.2.jar b/DepFiles/public/jackson-core-2.6.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..a7d87f067340ac378230a8ccf8b4dfc9512a94c1 GIT binary patch literal 258824 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%0ANnsM4|)f9WP^dq=- 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

F9Ck<@RiJoLX=RjXyZyA$)Ipeh4?TNP+Eozyr@& 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

SN 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 literal 0 HcmV?d00001 diff --git a/DepFiles/public/jackson-core-asl-1.9.11.jar b/DepFiles/public/jackson-core-asl-1.9.11.jar deleted file mode 100644 index 145fc4892222e773e7807b6cb25c9c7162f7bfac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232131 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%0Di*Io zWEA3s<6_qD1aP(N69v@H2h0SuM;&G+!@z=7RS+W`n7w}p*MQ6c{s47n>hdo=6j2AF zC%k-x1iUfRMFhpX+&gWY0oC{~`f2hZ3vD+p06>$#|9gS|{(m?w+5TGt;D0P_|FsyF zv9PtUF|hvMY;DQ^i5;Ifk@xmOj5VGPCJi(~4si`vY{8!6&A@)=kA2_Q-!=786Y zIOISmcur)b_YaaNMe|JqOcO*SMDtgprcT?WeS`@|kSSOpAYb@CLStUdX~T5DK@F)x ziB+|^;;;JRlgn&9awADaqwN|{z%XXOU6E~ii$*$gzI#$roTQSmzUc$ZSuMWx1K-9*DB=S?tw)0wi)_E-jY zUM4z2ZF|oCiTMufw4_xI;}od5;`$2h=%4G9ECmgh2|{i7{m|e{jHJ1MLnK=R?E#yu zd_lh)r2!DPOyl-RreVjpV_vC9(rKpY#QwHEh-=k{Ra;VcS85AeYt3;XwJZ!oe}879 z%}_2IiySj|p!<=6lhjq1Zld_F*#V$aINbTCgN+vE%8A8iSU6fLzgwA4demiQ0i6t~ z*@?vp{@Y%$V4PO#jSLkoWM^^1XQ*;P!sqav(yZCHWg-}G_ycIlViNjesrDvq^^Ebw zL-TG4+GJxYr7&wr%88#sZwUnu&P#&DF=^|c31n6Jpa)P?Q_Y~j|APDn#Mzq6 zay|YJPDc0N<`Dk>%U%Aj+wxy>mud|UZi!jHH%9S>rpoeZHgfokmkcRS?sM1+v_3Q4Az+I zB3nf+vBMUQjiI@JVv22CiOGGr&(W!`ozHLITi@GP-_F`>?;mX8rY!uYIo1z(U3}Cz z)enn*s#;&VT0M7wpUy5{4gSq@BY68?!acu<%igUgYWVUeD?s1F{M`jV>LH#U#nk0knfytxIygN`q<8}aaD({2=YLp)J{Sly^V0zRm6DT&yF2y~S z2hHj`tPsA!yJM(NV}ZuZZ>ZX5d4H{~E?IdscXQ}kH>b)>wzhXkGDAhJLWS)+u_kxz zxIgV$TtGg6dVi2xnPeVE3+{u_WUN<+BzVwcsr4uRlGa8OoY_VdmUiaz1s-<6kWp-?Of;x4_oXZ;7HbvNNE&c0P>4V(^&B4-h zSoLN-1#IOa;su%4ApFd<%)$sV7Q$a#a|tf21H5?xQg2prs6o{xm5mQNIK6A!V5`jnzI#u?~+3Q0oTRTkuBu(N&pGz-p z4k&aJb8OXO16K0^NlR$al!>rfiK^>kpG(4o?AIOJFdEenhGz?DGVRz`qitcviC_;5 z5+{VhVz^j{YO`(ho2y4LTRHfUwy3s3@tN?iB1s5s=^`wTA_c6;;ttSP)r`w3jip=h z4=ggFEr}r+425NG>JPll+=cs5;6P#u8qKGhs}lu43=-@#J~|GI2A7oDiARMJc}ksf zwwcMxy(NLAk#aT}q#2R1>sDSbU=e9L*X5HN1Q!OaWuiw99i*uaIP^vfmeea?oBI1h z1e!oRfGn9+@FXn}lv@qHXv(_=@%L@94&uFsF$+PHITO>7)vTY4F2$qeaUzdeG0n%bhtPR|$N*QKKSuq=ks8o`+#n?uP zfP?ntn*z7&QO-Y zAsK`NaFKfv!aD}u0S#)^{MZA*Q8f)gR4*f8eQnt#7*%{SPa)QR$56X!Sf^3VieR|w z3v%nsxytElEE;k;>;Zp>6MerFU3`TDVAEsXYAvcsXHZdTD? znFgk;m&ugD5JSN8kT6a$SAH-jZ}+d=)zNj#q*1hq&_&UktX@%CX%)Igr#z8{Mz59e zEdD6~CA}MP50oxmIVw4G8OJbgG)vJpq^VbpGwK)Y)U=Mox;xQl?iyX_n;J-Um&axd zn$cEU=)&ED9evoAgCiWDAzwMvQft+Z1OZVMS5^Z;5z&CeNPlb%<02hqZh_bPH-^+oN!tdB$_5 zb+~*Giy9UDVwO(#Fap*^ePJ@NX?Ie=76DfA z>0_blmb)duGO1PftCh5aKIkf!9+*!cp+K8ZoVLQR(;%r0Rj|sS>BRidX9Ak!Brfpm zR5;zn8&2|PJkX^@iiqLdN{{!2xL!F`?g_e8ey8if?oU5RYLeO)4*ly-=!tLzidk#B z(v46cT(v+C3K;Vn&x0KcMl6x#l{)bX5#{wRTk<}GbwOaKAe6*cxPgCWTlrf1>ayw~ zGn$7|>K&XjxCtQFFY#mqB0x6Xku)#t&aM=iMklk(Q%l!eGfvG@BjO+|WIP;ZI4(Yh zz3dji1J$C2zZfJzL5m7GQ_eXWSo+}O&G&K#EDf96;3vwPUEC<5^h0T(N+;Hr7796v zrOAbhR?fvkPmH#7cpyIND5r-YQ@=en%jaYkvsgvGcM138!fI!J5021>c6NF5^31fX z{1P3C+@J4iQ-l;ur4&qMBbWjw$gw&1)@}rL5tyYBVlQLGyi6_0wh>r>D`>7!eR5>W zv+ZB;z0$6KOOvCWIK4>79sMKHOII#(kn{nS8T-^8)!;>7nu}kpI^iiggzWmuNj=$a z>Dna8UL6IcQ&ib3&~$TV32j5A+L;*>y$*mb%!FMm(Zab0LwfFSBUygJj!oOBLkGjb zb}DvNDcnO+I(subKS#&yRSH4P)oEKfp40;oiGIKw;ihqncl^B7sV80#v$qhsx#A2S z1LNRc#;Ya94w_<78Z`x-83ZQ{hDyzU4bzz{OoPfPk}4iBpB5gqM%jWW6k4-MAr?hc zY%Z@%DLu4uX+%kdy3RQW)4MRG+_4#aIe6dv0Z{YI5RMhw`r6XWbmd965&1w|Yrzcid2`!gzZUGiw9omawdByNs zAB0khsTX5xmQuA_+q(qY&k}RUmhZIkEI6jr;#cQ%QFTL9*hE>trzt3nnV(|`p8d>U z$lnE=8^C=I5FQYYYtIVs*0~R{ZxD_hiv7f23_$PHAGXf{JPRPpxvvu-j&qN(uM_tU zuWyxW4;x_j%-;^^O9R~d&z=^}UA4cRQ=b>0-wfa`+#4LYH~C&C&fRuXytbnd;0f>h zGrZmiTkQ+Rz1kG%>tVJXk=g>Vd*up{XZ#O<-g?*UP8V3Eld+4=6a z5yPaWiBkMLexx{E>Lr3XkMV)y*<%yv9J)E*h~O`*_iIuF8DF~&_;`xfmm}VoO4NBI{q6 zkw>HFX;%_fP!@|HA$hy8lpnaIz2cXDtxRWrReBo+a|S`fM0!C-EmC2ixGreFFJ!)F zxVrZ4NWOi6zkQ*pdV_p(5wI{U_i--vxG(m-HzsT^%v>UU+m)WYd?S77=T?7npEG@o zKN1Rv3-p#9GMtPne4XNyansB2|HfxZS)k4m;m?Np9?#j~Jv|EY;dpf+e5hmx`O?cS z%!X*Yk1x=l>gYrFUG8j&t}ZxG*-cS9r5y8&v(eHzUAx?~t1zEHpFmFVjqNvl8Qw0I z`YP(B^2t78ZJXSPiC2RrM}<62ZpB14FTp;eu|r5}+%m0F55F6k++-?so-D^Dy#k~p zQ{M2zNb00B$)4gD>179~mQCq+KQKla=SVy}U7_RUkv7o%f@&^#MN>(k#VsP)S7DW2 zBw|I6mam{p^nV@I%F!F?TCOhyHQmE7(K(N$*_B^!X=d!}7hJ&Qbdfso_E59L(-&&n z769qW3||#G+LnY|l@4zSx^%^=-k~~U)|Q&?@L#sYY_w;yI+EL*(m~yeLOHQv;^bkw z|Jfc^Th!`G>X}9S@DE#` zCgzT*Jyc|MM1myNB({urtKpowD(U27gn%Io@;!zddHyonh<= z8hx|Xzuo70=k2;>GjiiPsLcvHH&H(EHb>60D>!J&m-=(@1gA*CsdOKmu%yo_<|ne7 zXc;mO>51x)JLxvBXA}nA(gEA-1J2}RxYZjfX*a^N8~*&B=2cs;_K89(1q!;=S#-co zJ6Ku-SnE-Pd+01M?nup>@c_55Ek%tW{rN|WZfHx9E0%7;S|ee-0ThLPM-I;$>vgeU zX+#RAi*CBAMq*lz>`}0pa&v&A2AO$Rmy=wsMD>7WG$o@J^{C=6SxPd!JnwDku{QwW zBg4QUV(L6q#j9|A6F-({o`yYFwO{W<}kH^#0+k z9vjB|t)3)$x`gOqn>iS~J($r)+_d?$cX;nvrmU~6W9oN4{J5&2N|5=oK?%B@1M+#m( zoT*6$(uZD4jVL8kt2RDK4)=sd{q~_c#xJ<}4$pW4R$4+$qZX+MS8w-!xPD6TQkDEl zX?bJ%ApbLwCFqCWolE*u-Ex^p;M_^+toxq-a8NPoAmka#shH-h?`(=R90@C?#-T<< z<|mH#6R!Nj(Xfxsoa+~1{kDckBV&P?Ii(m6nTk(y zjG(i}s}@<7T_JWllcyxN_^{N4vwEzE$yS2qI@w2;P%cxdm#xVt(YQ-&&KuxLZh;6= zIc?BEMZTK9j`yb#5)GHdXRz(JJ6E=$3;q&$M)D zad=_QI7w-#RVyxipyx(>lq1^6OJNUaWRdgl!mcXdQfG=)fd~Nq~M?EazF+@1d?C`fhN z30Vc@ciT9L`Fx|KIC%j+K#*~}DMkgP34&IExf#tIn)I01da-#sk)913OnKx!L@e9k zsUJPp`GO!;MB<_^I*#)Y|ESP&X4?j9;m!nahR5xeqxX>u->uQ_`*F_=pzw_$*j5@0 zamb6NM7?X)Fk{#P%}hNDR-a7xk|v5{xA%tL--)nk&l}*8FxVjAPOizS)esKixwe!LTj?#*>_xhUTS!)BBw`n>eSG*B|btXk=5u->fU>X z)ppJ3>Mx_yNE0am5>US~XD+3QQNd9LPi?PYy*j#ljSMryhinVlb1 zx1njOIEU$p^k-Y2`4PteIZ{ObBamcP6{u!ungnI1xw_Mbq3Fm|>GPDwHj~FKuksaY zqc|elqsmn#sOQ!0)SwvU*z&(f-|SCf+q9M@ld~4aq}fe6zyJ(fI9|2>P~us$-pL58 zQ%)K6RNqD{Es4!8=Vgp6U2T>Htt-t`ST5boU^u9qJXnDSuESJf>5r;vE!Nc;)<=#2 z;sh*m@}V`OME#%4*sa}ylpIE0_oh#}K>o3rO;56(0<9%@M#?$sEei3U^(wl(7#KPT z_bKdT@p`(1?1XENF@>@5Lo%*gZjxUjnb#Ye6>=$Mx z9y+iiW((czi0yCBg8gH(_s8aQE96f#Y)F0!4woxmuF9+4vr9HVl_x`NjuP9w7wTBUQU()$R1zMo6%y1u7cOQ*nZq3g|Lz$NQx;hAGg) z2HE!?(mxRTi|$-stqGcRV=n$eATtS;8j;TvG!tS5%!}$Y0VkTE%G`ID7l6?QuvYAu ze$67pPk}dm-bus%M##qauoRd6^-fw`YTAD`*if0{?$T9@wV)cc_!%ty){yj0&G@6H z6dPd40Y+j&*i5JahaWRj>3l)T8I~hC`U-)oXT&K1q(i`oZs8Y^SBsFn^qcpH^}L>s zT$obrEnHgOLt7H8iVKY15wj}>o0Z~uDlK$c$gC3k2-=E}jVn)%S3runEyVzHyvu)y zrYSBvOxy6SOmvg7xT7)Mlw|50&4Ai;h_OR*$%-=y$u_~!VEW5T>wyn=d;z2rV1zRG z37g^7Q%r6p-{p20=p)!k5D2n=}?noS~v0*Cq4D&<(jmdrRLIKq_<60QsgNMst^ zORgnhnJ+bA7Ntfuqq6i}2AZ72426HXtNwIf|DHd2xUG_e$fv;U{+sRN<(%?(@9O<> zzXSIPr1^q4^Z>eqPdW;uMm3OlW86J*Q4GXig?^h4kZOnuOHbG~;TiEpPr3@i(NEec zx~h4%Rpe1}VBz9Qm4woj6pSl#m-9zZuc1&MNu?&Vj>K#Qo|7U{m{&9LaBc752aNrqBec0v0&iVv19=0S?*9| zct{D@7074B7C~b^mek8fPGOv{D-z<5&rF{Zp|@pHn9?SltDoRrwgK^^=u$+U<}sQf z5i%NWF%fYP^WjQp4qN+Xu)35#OO z7!`N>Lgi^?zhf*HZBl2xNtGzGkmA*_UhJvKUJFEwCuI9n=J#fYc=Oy=i+8NIi2=CiPn&$K2yg(d;)sU5b2c5 zSx63ift5j7(gHmd7cehekh(TE#&SwIcuxumG0a#O_tRQcb?XJVI2-xod^wQ8U7agA zgk9GW!_L?3Q9vpd6GMMFV-FtWRBR)J9+cY;zETGz9dNacF9I1mV-G2d{li{y2mwd6 z_UM?t5n_hhoXpi(Zp~}d0O=x&Tm)GEyj`>8rdN&k8|R~Bl6C;67zXn)0!OucxHe2q zKQ>wjcx^6%z1v9pz-~Z&{6gh{U3{(L2!4#K@*q1=t2UmU?4~l_88N^*0Zc|)-|JoHo($Mutoyuqq{tLui=zFKUFJTTwLnjyhyHyi9d~bGgSdY#tYwP5RHy@I2PcvsHHOsxxos zVa;RKW)~f&S991JrNB?{i55&qaadeFkN@l^L`BD$mi8Y9jU}Vd=`52m4zi3>{aH2A zZ53N<^bhvT#3wHtVDJyfQpKv8wmB}456q?y5i@VHi0Yj^W{W7pd|u(Ox${bjmcQjH zGebu`PTFewUg4wwwh6a358Xm|`0LK3cts` zCO*Hh(`i&h;5+8Ozt-o06&^11*m|hfx?H`#Sw__Mm(ePne*Xu0KzpJ5iRoWG2L9jb z@&9RQ|Lo83w$xY@UEASHOLD5#cowArfM9E&NA4o{Ss@uNPwR0q{nSgulst?g- zC#ngq6~3@VW~3*?SY3XqB%l!j{V(1qF3Wp+3BmYG%JHk~CTQtZkIx@zj$_CT`(I z%7frIKN&R{Bh{SX<2B&S40wLK&`9pLn9?R45XN%(RithIo&pI`$W^(ueiza8ufaVZ zok`lK%?mu2M2>wexfCi|War6lp`YD%Nd(TC+3uePi_Z($^)1#H zP1QzCKfzzNz7JQMZSxzIOEjDBQu$%)WXd#5km?iG6kL4%V(XS@>zte_Nx3u=Cr66~ zAt>ar#BTm4P=^PAd%UYBqZ3*~yCZbJFyVrGs0cG3X{6W~c|z$}29dI82AMQ0gHQ&p zG2a-Hg|q>aXu>cc+obH$(t1HxR1@n&mbwr$4d_{eq!@$54XKyRujr^ED`AcXr8Z$s zz9-eN9LA@r{8N~2_7VxBrI0St0BW;VzMvexqvsgx3bF1Z~JRXx-^~vL^^*Oh^Xd2PAnT00s|~Wkv`^n*1AUM>MNoS+mZuAD!Tf9eOF4C&ge+K`MF7pY-*&nlq)JvSC(mK z*o1})ok*EF|4l0o$0n*XD?{johjR* z&3uc>+0I^EG2KBOTN2H5R4;Q+Ugf(ws;+GF?Xwdd7C+z0TSf-2n(IEQuD!6Pb>L_7 zLCQFJ2ZU63E%jn+h0Og%%*%&QSCCXWMF*5rHTf&057 z6TT$--@rpG~5+~X7ATJ706u=V%GyeJZfl+KrV7E0w9rpJQ{aop1r zV)9Xo6FAN?rJzh@tcjL|Ra+uTI0lD>?-jeoAvH=C4;HGvMgNed-wMh&Hpipt@6Hu7c_L<-9)Ox8xgx{X z)O@L$P~{X2_lCNH&59M%0+$C)<*c(4lV_Z}6X^0z9L>t|3KFVb&sa}!^_c6Dvb$WR_aFf=X#wWage#`^Sn#~PREXVB7BXmY0dyPUbb)jZluJ4?bm zZx4o^Xk~iGHW29GSlzHNqJO~yr;)MmN`@ElGTJ5<>;Aa|bau@O>5kjikMDRL<0ZRx%s$@YUqK%%Y66=Sicy8F~yiNv~8WfsE#Tx|e5y_2hwjr#dG9_RSTn@sF;DDW$1(4N}Z{d-| zG%|hVv!;OQ1aSv9s%Lg1#>7rf@X}<5P9YwG#rs|XAVClI1O@G=(m&*HCbuT^&(SE+ zO9l2YM8s2Qqgy421`%{@$31BR3FtizFf+J5?Kz{=H9??RsQxGRrLpFh9%ePRD zMW+=lAKI}(5FKa%=Zr-1BY25xteZ(GjvDI6@@}FHgAacj;Car7He-Au|U2TlJg><%Mi96-LxYrsh39Gg^?! z4b(}xgo!L;z+eL)Bm6n4V-d~>>~L!V0ZnvVW$oy;9)}h?v{1ZtX~r;i7R5HbkJfLj z@noT*guD?_6Hb)7i@a60wzv_CYz)hfx$gYKypQ;E7kzas!d^HV1Uxc=pt-VfVe`_2 z29~+G>i%t@bSU0Y+_Sw(bw|zi19WYmcR5r5YhY)7Nro>pry=TYjH9eaXXSF{oDpfq zD)an4I6zP(WBjL}!gIAXB63NR)QKJ^sTEpro$4KWSG6DB=`vw|8 zjj+{SkQU|6b2jV%BUMEe%})}q#*E>|Z7LU}q7dFAdUdyKaEl@6SLEkKzO$4i)Lklx z9s^3PnhXZ-f&=e47VO4`*_v(v0oQ~XX3^}jlfaP;r(Fv#w_CtP z2IEAC_5=9g-;#kcxFdhLr$xNcR(Svk$*Y(iZN9pV^B*Dxxtz8P*1us;*+E6wiTHTL$va84n>bdn)DShc6H%FLEqlZoLmWHAn8 z7G0QDU7$V7ZE+H4J(`DBZF+}*J0zXIy491PwI7CJIE zE*d7uhMBL zqefa2`>l!FW6g}1TLMA!!9|?hkp=Q?3g-Rp;)h_h-k#wTPZIeTH}=ftYi5FCNr^$#wNJL3nQLO zXau^)@Qvp#ye;Bfy96gtwy{Pk3%CJgW5)0ag%h2os2Co_JKYuBn?VBP9L^e{>0#cT z8rZ+Gc?QOWKAJG4Wpm(Ohq`*wN#elxXBFCwlA)yjRr_}`sw%1~wUre#byTHS3l=W0 z8nEl>OtU>qd?VfWVby0NR-&YLoM#%yj&YM@4xJT7#u(M?X%B$!V>uLp47{sCryK9k znSY4|uZxKR>`vVw|T*^gUpW1WM?Xnk-{j5-N1XZuji+ z^w)?Uj@cw?kUixVFwDgwj(Q3yL z+LI#AYiNi{w&_h*up1#yJ!vUE&UhLQYi452q*z*lV+7TH`Xs(&pNWojS)4|*5IqH1uOVv;(%@H8i0)a3^3`+<4Pg#Q{fhnfX@G#yA!#q|lN}o97vlqPn0A?HtM(I3mit#ea@;@hjzsH)&tW)ARlgdD^*Wqk|UR zzgkRz7x@kS+mb_gjj2bIG=gpvK+lw%8o>0y2AKBR5l0Xo37!f+KC`*&gbM4VcZf$- zQc+1#TUjxMcwIo5qj*O=F@-Z>1S1eKs7u!}($~+4|74=CrZR1Jz>u3_Jy?X3xMDIZ zE%kH}^$JPzm2XECS$qlou*LWd*-uk4c39un%tist+`{~(`4!yDy_>;^UO+`xYB96K zfU0MX@R_kf{|X-JJ=8M)u>5w8^yb^E%0ax)zDssUh-8*3@M}1`Jgxi9y@VSa6a2~^ z{uUYh5*3%wJdBF(2GD%;2+^4P^iJbr8apNA;ip7)2PvRy#TV^}sbe&+DuBA-X-q~<4n+!GVlfyY zn~6dP0Zz1lvYnm2_Fq}4Sr-N^YZNYvTRtx zf)im^wUru;jmGbAB`i=yC$@0v^`UGc=#_0?BXNa6#qBO$*)-kV!j}*4sSkc8$TYGR z*(=zn=s0+&z?LMcL{&sFI=yFKd)`Kzk&L2KEgJ+0+}z@1JY$NBg^Z6l=+Zw^mjNUO z$e2&B1;>1S9|8++uW6^bh8s8)#+Fa+U!BgQVT*VD*q1S&D9FF8VF@3Erb5YK%P_Gr zjjJ0a)-#f*0|^X4$XOjh0FyI3r#y0zFEE2*8a#z_eAaPYjs}geittI+c}ia(Us8ae zgKu~j;dtpO2_f6D#NvUAdD@0U7?s&iP4LR0GTU%YqJ(WZ+s3JI+ESq1pmTIYKgm7? zmnE^&34=ou;uTDfZj;Y53iw=6l9xVC>gJf?aQx<2S%m&SoSjp5B~Z6*Q?YH^=8kRK zb}AKBZ0^{$ZB%UAwpFp6TVFf(;k0vGI}i6^{f0T$TBG;zkL3ya!80r2NsA0VD;OrO z#x#8V1eE$Mtk)wTM?AQsa@yK@?LmEP2}WRv=q9nmn?NPeK}oK4WpjSwE%5l zNm^N`6;IsVDHD6x391}m)*GRsAUx9-G%Z6NlV~DQ#0TG53rr%TuB8=%fdXv(#gyDTE9C4euo5y!w{&uZLZP zZ^im^y@q98iJi2l*N}TQAG*_`9_e++=@tZT!on&z4qo2fd_QWGi_3&eWu=YM^QQ{1 zVbm+K>4%$QnNgpcv6SspOcaw}jD8L7Od#_cskWWLK@ijmP4scDez!g2memZ*#IQl8a> z?B5t&HFezx6Wn}3eKi`wq(yOKB)o+;HcmmYVVsDprijNW%|I*S?pbedgXOTMH-|?2 z#n>owa_ofy;Sv{>)8Y?xWE-pHyakI+Y~(g$mFp?|ptRAcd)y}vgcA;9sS%H~25WvnjhOYho$qAHsg zWv!M#53Pkho;PY0U58(i*jwdy+l$7=wk=@EpvIFO_thC7qRTFXhyJnE?gVjguQf<~ zsFt|oLHRA(#4u0P1m0zEFjppOw#H6z|X{uXUkLZOeLGpzfkC2ETjI ziCn9Wekgx|1m)PD;JDu*Ep%qWh<-BkpXEA5fW;fZ4=eXGD#(6US1-lp+dl|^w$v?J zK%x9jl|UD{-VC$FAbIgJ6>?4WuPpjyPiP3aZBsBHE)e{ikn}k1W5! zH=`I2mgm!tFii=^QF%0=?4$jNBA@H;>8)Lr_?-G6}Cn14a(*fWv3;(DJC0zxfaDZK3wpKzc9iRnFs4cUP zVu6xQ>mW=QfF_(hZ0yz#?XpBRi8Wn_G;|oRa;lNyzB%lNVk>A-){0q_h@Z(~j&l=L ze}GmQLJ#cjW1Eh#CZ`OkS@gMh1#fc0TWAy!2du%vp5Knc!zv}2-Xts|kTv%aKN~o> zioR*oDXVgswc@-PF3PFKyZZk+bzCV3t`UXNM@p|_lc>~obXR28v}2y!Ion1e3!l5o zqUuwKjB6_*c`q!8JZoMLU)V&1ae#sk zq_`f4)ZArtISfuo4R7_f6iCCAonhv%m*5S%#*+?)bw4xaic^QPYX-rbjggO)LVWN$KQLK@A`?uw8HBR|7Lcgk-&O_7XbNWxSIIK)nR|joLV4L%w!2tkl}74(J{82rLe~F z{%2?U^j~5a`|(ZNH30{gcq&yb1;B{u{Z-JbbB_^`P9(}ilLU^aGsKU#c!hTuUhy#D ziJDCyuH(AmC6Kn|%WO};b^~rC+`4|~LZ{k|Ki_Z#HyiY}%#}{gb^W(no(m7zxlYkf zY+Wo<1`la5-4QY}SxekREF;^@(_!qEs)U?FmjS1&c63Y3uB@jsUaQwbjCjXs!Nj51 zA(Jv@Bmb?Ji2+N6t%o6(2FGj@y;9s2>)}VKgA9AUSk@XzycN@^zfK=p0X=9pl5cjb zlRL4ORvc;nM90{@V?u2SF`b4eR!&Xdjg3HhALMfAiR;w>8z8aR)>^dpi^6vlKN&tPOPRY zjc^8}W`q53WvntkDb&>i!D@({W#Am(bY;0PNfn{$fPbo|+aqzs;OnaaGOc3105AQC zRux`CE`fxuV2yQn+Nd2cqFYm8ZUm>~Ee(!Uk^ECn4OpxC7Z|QwUePX%Evvpy9382j zL0cnVV}wS64NxASUSUWkNWXc#0+CD=Z{shl1v2;tM(R)88NG58O_*-VFDL}EyCyB` zHQh(giYD8p1D6=fS}+4j0jdHB%GOYKHkv1~W+*Vx*mgAUSI~ zxogB*e+M@{{!%nv#eTo!5 zhoF1_e?yk{B3mJ0ZTe1NSq@2TiYWgTyh4zcBe6!`KSkz%x9Uy`e~epnBOEuJXhco~ zf6z+c^opD|nYbsF0}8>1A@jnlH5?igS}L5zYs^4o9YF$dj&Mw~5{Wg|pw�pcp09 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 z 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

&R7;REBZd0NaNVcN6Eoy zzIyDIY@)aZyH0yJ8|zo4qI|P!k>nhi`Zs+K#eHSn4BvU z2F_MUiEGJpV~7jUWy?88U(!)p$SG*0Bt7)(WlFJN4P%Gsr2*ku;Ebl(GCe4tUG__< zk5Jg)60yMZQF%h7v_P2l4Gopa7w|D-hwRiRDn&GL9L_gtIFFZqVvEhaZDxb zntgQ!TPExEQL3=8-#I0_AHue9FMDrLgTXUaCdhynqpSSkp?`X5oI5dEIDIf{D@gvQ7^yh!n3poDBZ>f zkzY8ixg6vu@(ZrgefZV!s=Y*r!iKI!DYEy_#NT35br2bWn`s~f>YX9~s!RG&ZfX)F zWdyq%Zj@O8twnz90g}K+m&sXV(WUzW+4+Vws-T*fHFwLuFgd6C6`@qbO^X-^l*&d4 zGqP;;5++*!GJ9B#d)8YS1KKr033)JZEoNTN_r%9C!)vCSuoVY=zs8R%AhEB;%o?r4 z@g`d(V!=b8((9j{*gs~`+PcO3^l27NpXuTM-PHdw3sUC43?u4~Vf?`mNw#0DQkDS@ z3?l@Sfl;NW_BbfBqzXqwzPGZJ;5J_mJfA%FW~;5j3D6L0DL;P51{z_MPp5nx(x z@mUvp$h^`u@BVmuL+b%bK2wZoi01s}x^x6QTp{ zq%({Mu*6(s9GYq9pH!7h8vJo>Vjr1a6|#n!(n3 z2EE2bHizE4-{jzoM%_#(r@&b%l&^7=Z1jg(asRO9{Uh>!bGUw2LxeZlo+46Av-D2UtPC| zS@o^iL+O6^gOk46AtK*m8;P53AC5M!XU3;%m$`5Xq;Pd@SHD)5$kDV3{e3ycr7?{f zSGs&BLefL6Mc+`Ff@C*%T5hbFDN7*RY36h#1U@yT3S$Zd$N;=Y)T;6G}Z+#Wff$%zzxHoqN?xKrD2S zm4nNXk^})4&&tk0y5glciG5XZ(YU{km@(2Gu7iNQ=pE?*Az|xK$bSHeE80?+!F-qz zmQLKkIfYK^0y*|7fxGH{zb9$<;4p?61P#eWs^%1r9i-GUDU<`JWk#ivpc!z;8I|y|YL|5}UEYczRGv1^8X}V|wSqiim zY_HuvZ~^Y5cAu+*FB{1`Eh^*o^NOErSMGPN zR@lNcxrwN`);G_Tm!wdxHJpW0$`u||%NhoVd9d~H~DI5IqN zl*nal;=9X&?UOXD--7@-<5Xv^3(%zEsFAlNlc=?jnA2EtIT5q3p;6q=pSgG)LmCkm z!AciR@}VttRv@gJmiQ&06h#8;^V37uzbIPOo`e6Swb!QoNU3=4=C#Sg6e51wO8KZ2 zjL;C5@uJHjG*2e&xtPPl5-Hg6csToxTKx;o_foIZkl%vQEw;@!>bd)G@{koD?Sh0k zJzlJKja$dK>n7{Bwrn-<1%hS3KL_#%Abe89iDjwMY%`o1LM+Ozo{4t!TV`iGV3o)C zK*lwh9sT9fv!EA|6_kZ;ytWm5_5(y10iPJsQN$}%Bh)t$K z`x=iXRm<}u2>VW2y}Vwy?!j`u&T{?!Zk@M#+ZDtcB`&8>u>hkWjsgg=4sF@{Aw=t!{|6${0r&_YIRh+57|1EDdf#`Zdi((va3 zIyh0R!TW;>6Ud|AP(K1002ttDPQMlj4q7ro;8VixiH>aDIetD~WKTZd|1ZwoF+37( z+ZOJyzUs5z{hfF3XW!@CbAQy2`dPK+nrqH6 z#vJoy-R%>zhEm+1GdAVKNZKTUKMt1jPDzwD4Ck@&Mv8;DAolz#-*mbO){_Yh6p6aN z8WkuC+Vc8bE7>_iT z^S5{fNFosHGb8;fISub4r8=rX{$=+JkvR!h+E|+5V-1gJoDN~Z0u$nWvsAq5h zD4hnI7IfmW(p7oLl%e!hHys?(Z}Mj)SwC6#0<{9 zcxIC@GDAt2$}EB1xktfq%7+!W*=9u3Z|<+`Q6IUeCX?{rD}Iga9hqV`at&%wc`7Q+ zM&n`ME_v1%B7ITO5!Exe;W3@U!oajU9JUDGd>AU1M^sSf0Iaf|xin6ZK9rw`qzWvb z#y}tJX^90+^^+->o6O0)Co)L23Z;y269D{9Pi#+jr{b?cSCJjfi8B}CeBya(%~Q?Q zn|z?EpCRV(x=3f_tv+A&FeG5+dzmF6BKeB1wxJp}E(-jRX_4~kPqey&PjG<{Zem)u zx??b{c}5BUazgViu%{`>Euhq=m$r>hB{#?*v$^z+QDPzQ0@vbz$xm@}su}=MRArGNsM0cWtWwYmOW&kr zQFoWfwE9S?f8(%UqwEe18Z-JJYXb>a4UrU59eRrFAwE&FL)gO*Uy_e8pWxwAn9<2* z(7O4x3zfec?o;2=d^N6N(fi*hz(1E_WR1vw9M~|H0|znx=luMSOY#33PbtaTp)ex) zY*{y4vgH#dT`PeGC7=%=*ObFh_?1cQA}(|`HYyRSJax@(&I{FRCIMfE67wZBbkdtd{8~Gt9HnE(Yr? zlaPEDJV_RdyD=6jH0%*%+78BSHA-oUIn?~`FYp(zq#%ZC)ZHsR*q4i%V1q7g9>mm6 zcTN#j^fV#oHd$ z+d`<@&Tg@56w%|h&vQJ$*nKtzXd?()%zp?_mt4&_ssDRWO!r!+)B?LeBw%3B{a<#2 z|IKwr$NcGW5k)^#Y?@&_qg;&;H`QC^=#$b!lN=i>-K5!G?XL6Dhsc?e7dr z*vM6hliH4-P2{_-X@lLa|zY>0$wosZu#Zwtx2#C%NtEsRe)*)n2mRfR!J z7z9mQvc_3AsM?kA@iRIQ67H^yRd({a8%CT+V-qhCx~RVa zyXzGMlAjz)tO3zazv29_>3X~7##cs0>7QvhVxCOG+p`&Kr>i*UCDrX&VOM&ZH--~= z{0$3lTt-JXgKq{so@K2W#}sqT+_R>=z7cXw7NS_$-gHkk3?Iwtwc2~Nrwor<@Vv^? zx^W1;@7E`8UO7Z&>(fhPMLDrOseb$|oBlyLr4FTH?t$e+A=0N$1pn#c{58hm_}9#8 zwfZkN6jjuBJshq}2HNIeAv^RH6LiGX58FkMB>DB(m$0Fvrj1+Sf1Lon0@czyZRe+)rvi5d{S@Oo&Pnp<*x= zM^TSTdFdbk4F;KqvuaO{M77A^mSu7hid}v>2wvR3vH(3?%D9ccV2>i^_M|Ps?4~N> z_~v^6hRQ^Lr1u$kZPvl8G*GZjz43i)ei`+9O#yT%YL@93IJ=ofE4xb8U?z`eq`2Mv z2`=9qU96n_eNDiVN>lyOHLQx3#j;FV2<;5A^t$luZj?!fBDaxd2<}g|{SK>=YID1x zv)f>9a4NPmk~YOWo(&!X#50*M*r1JuB_Dvz8ajN}yPJake0SLS<+No0-1#>_roQFJN zjRjIhF?rX<3HMc-SOqZ!sg!^^$6cDhymOE7U0rHmd6F%>Bj5Fc&G5<3`SLFFDag@A z=0q|O?N;}G!)^xh5|#u&q3H6w8{u$}$`EwT?p3Vj2%nLfLIU#xZXsf$d1evLB#`>NN{|45sz1zXJ! zEx63Rnj{#sp<{U_QBTRr9hDIte@7Ss-{KkbnB}X8PCuFTY#ppNYS!`%G#^?TH|Hqc zSlXyy%PMo>X;^B!EH+>NF--T&4e4)I?_ggD3>NQbJ5=w=U7~Ij4dgu49}W|f?J2qr zlKsA4XKr|X4OAgfU&{c@zH>LszPrgjD(PVMJop?rC~CEd5JTgi#NLu=C=*1nefv$| z)$Ij(ud&~IRNki7-m0*m5`I!`EJV9k=VDVWDl2JsT#}&WL*Pay&;kZ zh<1wt8_8Ev=vhq20ID4p@>zPzpR_8z67xtWyk-$-F;)F^9V_aILdt=5hU9x})pkmi zeetb}Sae_-V)b+qz3En})ih8YQyuXbd<1WK`!ekqQxc&Ur1#?!9Tb(|FXR=CDq|;~ z`w6^SI~zd-QoItairUSR%eH+)kkrZL#t`Q#rmQxbm^P-~jDQswlCV5Fz#8PaG=Wcz zZ71a6!Q3ZtbUdSZ+v26;AAny1J|N2MJ~LdQX_kN;FDj=>k5AI4PFzwyAd1Mor37ZdevCQ#!Y=pWjIKg(xyt=nWM7 z%%-3(Z~P^$AYg=TS8&a!f$mH*eoWCkUQtEA#GzfJD(<`zCaJmz`wj7DHi}wD**A@| zS=#v4=MkT7ti^6Dq`59rrq>}*(aL(X^-r9JM2Z3`wBbSW4^VP9=SMH+poCL-zRKh9 z6Udmtw6mLv>Zzh{6sF3VgIP4#TH=hYW^nhetvcfSzZHrYlLlWUa8|Pi18C6ord}EZ z`&^6|DVw53*21h=TclN&-Bo;7+?#NDl?G+!rcIm;30)1yDHp{??=U?n4+6f=URSQm z@s2CMn#MYLUY*e2xQ-liNXDwrX>oh!swtK=5q#rQ-I=cVo}jAys>#}$yO}_O(Z(Az zGFl|2HDVU&y^PwBmQXyJIM!Iklxbj(I=EgfI3Ifcy^B_MdmR= zd7y0gHt6Ay?&2|@EpgeHY8K6UPkTsuG^s0+*gAf18E@9W+EqYgOtlVT> zyHR5cr9DDH8=bn1JoKG>;}HGFv<8YvrduA=KFr1RCy1aZzzqz_lCE-z-22R|BS>SzEpF z+Ll(UiEENDAhVCc2AFbXKQUXLiNgKNIi@Cj z$v>0oO-}Q6WJ)_0Brsk+RwOBV7(pnunlwZ<9OaPIyq@F*3LUpD-=r1+Ba!r5V|~;N zn1Ku_F$5VR5A0WOMg0S0mShcO=PQOBueD!8=0HJMw$aXnM3CGpeA{QU5;+y-V0GqJ zEP;N-al0xZ?4w|%{0;Z5LKlYNe4Lt5G?8CDvI~^^Sh=FrUO=kepe}QW;K!j;OsPf+ zrEm`Br5YBKMtiVf1_}YZm#cp11yLO18iMce|BdARu}9e8Sy;{j9?>}Pga4n7>aQ8c zzlvcO8Re5obp&Z+b!QczVOwEbAEc`g}d!j6Rxw{hR?$cnAzan(A?v7y_UAIVj&^ zrVj3!M_9PSD}#{D8iPZ4JPmLQ{UhyoDk+6sa*oaPJcF~EGvpZ|n9**_9_ulBbxOyz zmprd(UmM-6A@Mb4TG!4G4z-=#4g-^}Xc)bzQ)_9Vs{Nv_y!}cbo#2==gq3|p_e+H$ zdvh^qh%T!DoTY)O5VceTYY*IKt9*I~b+;(d={j1ald6rqz`)D@Oucv3noZd)pR?Q5#cnNubF?eNlK%EJqCO(!fu9GdNwB7THnNPN$F< zY(N~b(YoVqi6VE(R>}H{6^6B?F*ycZ`axvNzNm?-hnD;#_am=^6dPxIYv6$G0_%41 zj#DQq$b@5t z%0yZ(O)5JfJ^^En23CfUMkLd5D{WmT8;C1x1o|Y}y!joan?T-dD5E{E5K1R?Czk0g z<1ZE6H54#bB%k`zN5Xr5XCe(-GG)w{P@g+Zt#%hW-BFpCGbQ1pxF&FfVW4_v446Ih zgzulo8-BH8E+UpnN1<^Je1!*q_hIbal8T0mu#lVHBEj_-s)N8yJ( ziTQJHOn-`4E<;54TrFMoD?Yzd&pDu989gT#l}B#R?3KunyEm@9l?y|OhV~;FFfH~K zOH11JM~40mSNP-dxGGhAM?Y(<{OldfuN!8)LZA}+5{VtIu*9RP9kb-f6n3h`zOBWqUDHS7K7dR>iJu4OH;BQ)j4{L}rvue{OAyh-&}-c7?cUDz~&Q=Ny|BgIl1F z`{s@Dg=Msddymyo7evarsp zC!KE$z}-(rV|m~cy?_qKCW(@QA^1u5HCdrtAiH}mK^nFrp75CrdR=&}>l`vz4L^FA zq@2b=q4x`XEMm-zWL1GfNE&;x4CI7-5~F<3kta0?=?Th%k0b7HCPO9hk#I~$KKR7{{zyX}QOVlY8 z-|+f4oxE1R+w+9m!MIye>|i->h-%a=-No)U)uYHZ5xOT)9irM_hG7~A(jxv@#PU$4 zla=O~qYpIu9=m~5&jCT#aHy1Lf=evQaa(080cq5nZ?a4xyqps^iN!h=80~YnMO5RH z5jt3;+k`_S(?*E6(HRO*&V0r)>9dqr*EM3E8e;13(~<6-5mt9;vnHeiT;g0p)P!p`l|(`s%C;x;togWOKOiFI!V zQJaVK3@{7VV*U%u&o4N^oW=vx69;=yT>OzX^= z7TJ##71?`&grY%FZxwLG7gWo9(w%){+$HGsO;;xE2W?GrN{W-r0DLK)MoP7dx!;b* zQyX62jwcxSzxlkw>@ae{Kb!y3u*(ZrqB(0j@8iXP@@axdv3BK97^dL>UKXrAZXacCMH-ss_+STe}f`g=6wh`~vfW>8!Y^R%ijHwv1ax zS7ErSH8^ihVb#|+?B5KoV5#h;72lS&c>gVGEJ{gP@o)ICOrX)Ecx{kMFp8l(^l> z&ed;!qc;B>JPZrF4mz-ItpGxWg#W1^`eU)_0wj_CiI*oU>;6#$@Xn{uZQ5EuR1tv| zh_2a~?9K|f*HMN`{_M4J-?dnmYY}egob=6mr^!wrxdQ8&4wYV zv+vNZ&>{DQxAGdXVdE!cij(Cpp_)`$otk;-y3#O-%|j^bWo}hsPmFEeBsqqIImraNBYB{&<>R z&Ca3GPJL6$cnlIY}8I=%Btb$XVqnEr!PCvpp;6sXg z6Sk#JsU#aH&w9W!jDEy_Lm-WJxw94GjX_x_Xr5eIFOWLiV#_g1=SkLpt;hEL8~*$! zsD8-n)+qq9i7ha&2>w%0DgC1u{bx-109zg~AO~a7y6LKGU9JCy%=0j#W>0w{d8>pP zbe1o5z0TG|xeoqR_(NI{iRX`N$eyvT;DC(~Q(|g*YHE7ybgSDZ-mj?fgEGNT0AQ!49XhL`g(Xyp5(9*v#O5ISJlC2sdV9FJdub`mi3t75m4%vT4P% zf4h$16`}%Tb?AyehxL&YAkK>P1A%*NE;;eC1qMmR2Tvb95nfO=UODBIJBN%4`&u?@ zM>RLvFBD(W2PfA)hwt|4bm8BVP~;(mrfl6g)Rl2uSga4?S^)*43i24e3bLI=hHokl z&a(AN-y|v;FQ=eF7ia^qhsR8+!@tAPqCilo$A3rRvKlzU>+*BakN1c$qFK8~1(UFz zu^sLfZ7Et+7*gxoiCc7QzhYk5&o%x@&6!GKrWjt>QZKxe*B&{Umc=3*+lTS5J&%qF zM=%F4ua5xR1M2^D8n$-M7N!UI5V-Gz(ZIG;jzJ zRFR$liZ7wD(wbw2l3bEhXSH)!l*~-~Uv9)Xw`7f=ivu6~O`ooKkNKu#fr7Z$+)sE0 z!s4IFGG=0iEj%o7m->Hf@uNVb4={1M_8EYMouZGZ5z(5?)fA{Wv`fl_3ZRnS+tRK?aU9x zwA{Iz6zOx5E2Q(Nu4MN7qOV>!FD)*{-LFX~d`D7`e%}t#LD}!It`n|N!Q0v7qzlpk znDTm4S{S)&hURIFt`NdJ)Ux$jTpLJ_Ast2%#?@cZM$1*WUWOpu^ts>yF#^+5sGp2u zD$Ua&=wL~;Nmst((C}U@p`(w(x(Rb!73_5OnVv|LfR@*Ewy@lb%nR8bXRL_(ewY^u zrU?{T9j67#Ms=u>BHE~Ou}v$MhwQIztsI>}ACjxqQ$xpi$5N}tvO3yVB5*m> zKn?GD7%nbXCuF0*D!I(xys#AbL3-Y<^~gS8w)xw_Yl2ZrDDE}Cxhp0>Sf==sC_$>- zh7hu>mI5Yw(i);DRK&ZSvDVHH%7}ijGDLYnZUM3ZJ3khR1$oCWCa3&I+I#D^xQTwK z9Fb+IG%oq18+MP(QKCN|tNKcmiRsb6IHm6D-R&vpWTE_^SN>MXL(ig_s2c3;prUkJ za5m5}?u74s$erktEMt?O+YOCNP>wX@qBF?4<~ApdCxn44#kxkG6!frgQiI{s^f+&k z>D~7bRVv>z*FOwZ92$f4e&lcGQ3t7IH}Cmb7Bi22MwiPUiU(8k^Y?4Xtcy*N3fDkX z3K6diF#ZW)&x%g--`ssBEZ)_3?4FN+*!i;O=PWo2xL?mvv9Umg$DQRm(rs5QQ` zVtzGoC>WvkWz#NA^)T_AgwNw%JY>YyY9Q!2_s4ZxZT zbC(lB%Q2hOM531{8;q>VAC*Q{V+e3UK3k3PY;o7?8+Q)|>89EX2qM!lhWGK16=K#i zgO}Zko9Q#wS~-w~7G#6oqe1bO9#|*ZLb|Q_^i2d$@i$5dR{<55>A3UNtQrj(3;GD0_R z1k&Q7BxJ7*U8D&Hc$J=pADq+8!`v;lQB)b>=UI?Ji6Pl3#j5*yKeT(>!nP5D^)qSS z(gGNAwy(KfCahEYtZixR)!8}_4kW}&38koqk&y{a8YuS1ab8pV<^$+u*|_WFqcYxh z)C}uPmi02y?adT4svqpkjW!-bl$~(IoW1g)HU`Hg_=?+QDKo25OM$LsDhd&g6&(Q6 zMA3vo#X*J)ZZ57en8SEt*P+&_YV#arT+IQwkQIF>L%!j2&c)FT_yH72lAmRBl?^D> z_B&0?jQj|Darij(qp>#b0s>a(G#N7mlDg_F%nR9RGVtc1E{iIzryW^_JigY0Z%Hva z1aww~`R~R&-Te8MB?apiga#(9Rq?dZ2Mc2DiiNz<>DWUY^uJ~U^^mTA-SW}FYM9u_ zr&hKjt;;q#|QZp=Eii2fnsV1j(17 zANzPuIx`q3<^I0Kx_1XDz)9OUj?9>>X_X{x;dYrxb3uk+utBY3t4%_=4aV2sqvi*q z2M~%kVUg%04e8-J^b*F>f}O1m1g|L#1a?Sc<<_8ZaKN=Y9l|XK(;P1*Dn_cZG3bmg zsyK(II(*!Nr|m+k2WvTG*Ot6v^vo$dDu?1|i9Z@o)Zm)9*zN~wyx}I7dWz{B`c$HI zX*7Qwy*Z~VR3j?jrU?%ud6vbciXwE5!0!f(9TZ%{&{xB*c)8`pu#1R3Cb=h*d_fx* z``xny8FN@?akWWMA@)UAlk%O2v=;X}_vMj~^#bH?$6sl9zb+qOL2vRBHevEQM$EM9 zG8etD)n~AE(H2~1t?|DD8Iyqd8%VPqoD8v9S@(dgOp5Ma(N<+`$sc;=D9z0p5nL)L zD+qN$Bf<43UzFqp19Z`E96%oxeso;A(WLuxsAw`p&#m|3LwDN1%+V%!*so01MXW7S zh!aO=a0uIA4;mU2Zk~`KfOw(r+;z649PC);vkB-3?pbQb!Cpi(wn0inTvm|p+)>eF z3k{-$*(~qo_BQ0RIT{^)VN+zp*xLD2+uD0>BmB4ryuHJq+ldcH>?Ub6_>c3C~OFHsG;{M{Yd`@J0DUzY@^$e!X*mZSeo9;|*cL ztRbk_c=rf*#wpy7s@9%uGc&E5D0B?I>NC{4V8G0(F?L;AANw2a;R6#v!!&3#)%u%D zTz17ZMit&&i>PlO?>yqhQdVK^=rQisMK*C(B1{sIFJ5e#OGOGIyI>2|KZ3EB~+_q_0H{*B7 zOMT}ypeX*?ET2h1F@XFwX%KC14NMenGy8@WK{S$p850>#Y~e8{bvSyYa02Ov+^w^D zxP=2>gl0ae(C*fqL6CHeQZUG`MpRstH*O!i9JA7;WF@?!56kNAt+ULk?$tBxD!#O< z_rD?2e_p=?G24t^z-Ay6*kS&sJ(mB+*YBU*L9()}9dMh2Pi7mNgREt-Lbo(52zF=m zX)qW8iAqF}v8QFFGD8< zylnT0ZPvyBf&FKbVgEoyw04_KzRfN|e;-opx=*~QRQI?^1_MoKIW}adDABzDnk13R zjdjaMSnq^5lhwA_%}0~WoDPEVK(j@iHVOBk7RBYGM#3&ii2Ws;?+4lZsu@GeTz8&` zP85n2gS>{EM!Coc%RYz_$A6fCYS^^qnS)M}YrMVI91%KA02V#5f{ zhLL&7624ISVL%pE2ihD@JsvrcJ4=PiVmZ$2NpWz*fKz#*(>KCZ-UIQhv4(6Bhm29< zXQhL}COg2S;OCq`NbAq&Q=Dm`c7V-ZNFJ_tI1>YRh0TuP&wIv#i~LYLGH6C(i3&g^}YRquxgzSp@_Pb@a#mp9+k8p+I$N-+V!dij#I4M!av zkDnwvprU8eLWS>mEWSuCNp|FXgFC9mFFg9CukfLv#b&`)9_+8Kk+(|elKL*1fMJdq zq*LSpol1t3o3LQU;(d)`nG2W@T0I=HD!4C!Yh_`}3YuRt(aHB~>~Ybd?W*1N4BzZw z(kg0(%Itq;hVHUj@cp;Ru&*rkaX*1^yh{r&Uv z+#lT^pKjnbAx&=t+{W2tc$#jZsL|KZ)vzv%@LHAQ(Hy4gR;1?u_9_(VpDpr}Bmy@b zPtgL?fg!|ia`WM=BuV#~>7ZRkrB1sn+mTKQ_fachA1B1kNqAs1_70#e7*_g2qo__)b#eDKPsaiI&fT z2a0p8bw_z%(}G|vV6e+yC;D3Jt_;BNY`z7vFnlk&>H_ zz96kbQFfzeyx84O5Y4p=B`U7%3?!V2lC2DmU*}I}ZD^2pliFgqO&)Y>)#3RLM z`%D>r%Lw7)G;jZFCC)H#j{hfy{QuW6^p~1HS$X}>W*OehA~pvZt73(#;@}xJSDe5& z_fJ7VQt@#z_!5gx)s2!ZOl(@~s^j{EigE5cV9&tCGKoVQ;5F6R>r)ds>Dh0OZ{zBp zzRXw|BDAt<)*Bcs_Tg@@&N4d00(Qg1v;8H6Nw-ay!m&~UMWSE(c~a7A?>NxMzA0Yu z0~i|WWw@WPzVq_}k4_lN@$&4)=>Db_B%d$vz(97G3H#A-mmD0@~k^4P~R=z@WQzb#p++~xgWMe}n?L)1{&FAOB0qC%wb$R~A zOdh#FN1@^4b#6cA3r}$4DWHWOzpudR0mnDVi@p)N3(^JU15HEXAerhc6l8rD>gHp3j( z4%m}=pEW}uM6B}FJGdD&{QNv8-4t;I=lK@eS?9F=SVwH1QJ#__8)HpNdM+zrb% zS#h^9h$@AHjq{0vu2Ih`gy302aML6RI4GPbieEFx#irH#Z*vfL8dh|pz!UHU_SOHp zs)FSo6nODplWnHj8s^JNbbAojNprT8uo3PcXAtEHNf}VR1Fgj}r*ST+7q-x^pIN(q z!+Mh;!Si9}zLCDe{G1RP7fqzAmoz)d0#4dodpnu>zW)9V-@}pPQh?I&)sb<40a+Zc z&;b)OvA-=MlX0UjC}N$8468|YUP@IKu&8^*NFsfHRARB<1HdBP{iO_d=_~7A&KROI z>aZO3vxC;Vzj~d`4SxMrR8>VrpQGirz%~^uv(s_CDX#}qnA0i3hkQP_sc{@`R?{q* z**Q6Aa{T8q^@c8!I?dffQdWcoZDvi}qdzm%DYKtcc51sB?fkuQ`LCNMB@~so22tz> zeEIsWSqWyRfKz8HXRM*qeii>2BPVUNZbE(!sw*avfRjrxxPEgetjsWfnT@Ii%n)sB z>Z{x?8igXv-!Th}*G|h1xE>j}YKanIZ02`)9q8E|+Pk8JqEdLX&MHUw%2|!nDf`e~ zHvTWHW~zg{@K21zyKu^3ffHcP?ohHSm-yaj~kj*gZXnKbp z!z+M;CH68mdiHBQk%h)tW+pjg=o zh|X}dpMJ+EeY9Xv*dSph)MaX!1>EOuxCRw8ib$b^%MZhYE)>rItw^Nee@nAHGx1T))E zx2m*8bl(RF7>}0}PI}$$mAqHyRGtC{GaYQ^DZE(RHkd7PP}8K|zpV62l%x6IH0d$| z_ch6mYn0hlV@)-5t1%lJcUCQ{IS(g!eK1_8%BmZ@2Gtc`S352O5cn~Z*_qED;g;%b-YNBmhJ9<1y{D`KZG+QuhKoeYY6khK<|F@XZdy8m6|s6QGqxY9=$i!~t|z zwp}gii3sASpl`IB5>f?hb2A4uSOQqE9h#NgRa(D`%A)h4f-qmPt zD_O9EQe!*eq+CiU%~o8gJ>_sG$xJfJ?`L-C?R!0v#+t{FgI>0%CvT3MEo;V9Uo%d)9&89H`+Smq*Iwx3~o z78li`NM$CT$}c-FvyMEu-n8J`_Q{x^3`d0UWi8qxTw7ua3H77tEXJj1iAB(evFM~4 zqJ~59%`=4HfVOT`?6U%gG5noAOaIKXU80)v{_Mu;9i!?WDLh(QqK@l`BLU-aQeJ3h zha)FNfW~3r&pa6xtsmGk*-DvthR$X>-HQ$Yle9$Fwj%Lp!$DvV%l!#HUg?kmY7;iT zoQIzJrLQ?spC6LIK4*=JS4c;`SYM!sd3rWwickC`y@RW)CYW+s8KD5>+Tl(w4;)`j zcmIkgj|EGNLKf1paBBFNs^8i%nFH)pA4x0wBU4)26l3?IG8ajAq#OI!J{B(#N43Ng za{kk&z$c3?kqbb=k1EWp;_NVZNU(?YfwKOEejWOIqhs9ZJYJ4K)M^W+kvPE?h(MCA zE#o-8a!A}>ES*vppe%4?M{Eb%Sk4d;ddeVciQCvd6^;*T!jJ!XA4?>VIj!g?AC^qK zU^hgd)d;RZkHtQ_H!Ng!FV@7I7C~UB@y&?_q!2>eK65NYtag!0jspg!8>9Utr`FrV zFe)(tmGE_eRjL1W{**L~^7{QPV{lf@yU#K{TDbEOc~htx(>K8vq|5y5iQJhZQgbzs zAM@aN1S}lh>zrmp&jJ1W(hob-F%h*-@bMzV{@}Jw&Ntf$EJGe-p-{)I@&;m;;lFV@ z)b;I+%k6nPIdEf@FZL$rKPxdk7C&^z5VF-gWc2WE+eMuF_%fd1H(VsDbNzkH{7+qE zgIe%{0L(n`K+|uY|1tCY4do{JQxW|e=vHe$r<3W~}y29b%5eT^3Nof@;%lzrq%djoH~qI1035(uvxLX{)0mfc0% zNWd-Htx*2aMe&z2wx>C{O|qya-TP-ccKF!ki-*mQP0-wFM75svUoUEu67V)4v1ny ztPJT&o#JJyCI*O0f>+9R=5(yM_RS0rV}@|=O?agkxw8_&W(>sx9Iw;7kFn_5RN7*U zurBMgT)*zOd9Vd`Le|cWk|b-Jm1jM9o9cZwZbk|Ohmsz3$k#2j7pi*d7;RDi}Mc9$Mh=W4Of1)M}B1#K;Jb@zgy zZJ)V^iF3hOGn05Cs128JgOohvAWm*+bi25BB$IAWxpSR+G^ z{wAJX#t(Bk%rf(i76P*KVOl)PRi`ln9f=%yinIkG#6opP~q^_5+4H{%b$S$%ys1*?gKOxiNma+Q}qgz1V<^wfA^ zpsq9OV&u$k8K|BFE|V z!8h^~!6JwEzw>+ddEk$e;SHwu043$TMfC+aOjH0+P9ZMH7NnAFgazT7_rs6)z1#rS zlXnKf<|TH4XH0_a-&lDwzy9Sb*|*>iPEa^l`aD}*F2TALA}M&yFo0Y*<#aydYY$iZ&`W|RV(x>eoP($pDzu<)BM}844DHhFO zhx>@+gKAcc!Gy#WW>h5%VLRLvsS`}P{HAM<78l8+$d}@df5nFQ;U6hM6#Pk3XAqo> zve-)HoU+Iy6(O^DcEmA*FWDS&m(SxhErKzE`#FdrmnN0MMA)^EHq&2Tap0AxbJ}?O z6ve;DsUhwDK#^eHhA0-F!sqW&r9XK_zP>&61=xS(|F3&^)_>GBe-tbWKvKIgY3WkU z99{5LOgGpDPzl^iG8BX;A)|)($pgQmu~T?#;UVy(R`FXvxcOcne-FONcV<}Fp-@^I zF6O7H_R{06_WPIVY5h+*1!s)F!3_&PDu2Ri5*72ux>Ir_eSXj6@sAYot2HWyojhEKt9bkpBl8y}jPj2jWX(QjP0r|W1~*&gThT$s?F7?WE-uw( zNy|~IZ!5_jgisSR<#F3so+LUq?i%G;`wWn7_rf$==g}LzOskz1>4S#SX9usaMBJgT zY08>xs#!(tDuJy9m?gZQsqxmJcoud&?d=DJ!SVxDzgR&@Op%WD+U z&YHI@45}1lk&DaMP0t3@(=*qT-x=~wj}r^$b&5P-21t@S)n``#6Njuzk8%7lFO&W& zdE9#>X-7HR=0?W#ISPn*$^I zI~X+3^JPbzkdD&Ou&=-I`qydaypM0!w@(}cYEfJrNNhyb$XxA?S|vt@jy97gzXJr= zgVeS&Hl+85nP^q~4heh;hNl4VgKhX)r>G0$Tp!mtH^6X1NAu- zcR_`}!3X?{?;XCxtH;24 z$VV_3Tq!*J^;v=B<&}X*nmbGw5BEmI(#Md6#T}AiWu0r&^uMsTE+F;>nQUa!^$+Z= zk3rl02efepG>qwqD#iVTmrmHuZkdvLDFEDvekxJndt3*{F?n1Uv2R~Ftu|HEo=U~8 zbbRz)cepB!ZW~)hopoB9K_SHSKt{AnoAWE@T2GzgrdRBjfpnqG57q2t^S7|deE!S7 zur~sfdiptQud+Rn2dtD2N01+1aJE!V?7m?27Y~?(jaGT2d^}at&=_h`|!VtWk zL9@m`)K=%pm#FE0}MLR}`#71g|Iin9G>1nw0Zc# zsF?vkzIBf+RXQgw%-DIfw;A9W2iIJx47B9Ucf6Q%i>FXDM#p2ANZGm;{x1rE z3vG<&9uFNzB$X{JI$-v$NJg+AYXjli9I#c+o#CvthVlZA(W0{-NWTb9wKQC2LXPu5 z*8PJmJ~Q9YR&i>CiVy4O!NbK?dR^b-a446({HVCcwn21S{4(TYVy-?V*ZgEm_lP2UK z*P^)bet=JCa|=%1^B2D7KV*?w&OL+bGm0-zngAJqJ=i9=1|71FFzUi6`tWmRAOnEZ zEYCnze&M*EwBt=ZPm6#ne%j=WwoI{0;()t6euC4cxn>K1mQBbo&g{Ek9tAEye*^#5 zJXFl2P5XzBlm^;i(f{A{DgTO!M60X==i?E1<>swB;UDxUl7!agaFD4*Jg5lrg^Hw* zk~1oNT-oII%sV5k?ScpJZ`gSVZ+rf1>aE8~P{_Cti!d9?ahi%6Ueo{a?G^k6l$(#N zZWl1$pAuRM_pT`py;XIeVnBgET(2OGw5(+wGG~P)eV+m_bi2;VcTZxWv}hqSx@GLL zhH@FgLAs<_NQe_Z){(?{Vv8&x4&T?LRlLm0L{`jGlExnhkEg|QBu?bukloK%5S!^G z?AIl3%CGL&9Sb89HhKE?>Xk1{%CGsHUu{*wxniJ!|0OSm-y0zqwkg!+th7|K*wke> zqGprGlgb$zt5jWS%1i?{T})JS$rr~22OfL_kUUPI>a6_II`ZZu-X%kR)%@P)>R5o8 zQoX@lJukiNs=t!#un~h|5_xwnSs|pD?CR+x?Bua^sQw-9i!z*7d#U7CbPN$5+t1|D zHBhHR2#)=&A=`(0e&zf;Dhhe6*58y3TDY`IDLo}q;onA1Jq1( z=IX9WUX7_Wj?(>ql)YnkrCZiET1h3T*tTuEDz@rzq3cvj20c-7@#ZeFx^#r(GZ%EAfBRqCeRa?3^rY0e`xlMhz%0)OoCr zpQiwq`ZO?g2SiY8MC1B)B7bpIVs`Q#;e_wxhM&zdPi5l=#sN!xpp795O)Inbs<|s3 z%13^5B@*ALEwvgOSENqn`|$BQPc3ZA-?}*J>+3Umb{C0%?sjt6ZoK#|et7@RxE!{6 zzy+xhOl-LVX8`LO9#3gmJ{Z7zti#c}(*KF`rK1JK^=fWi*NyRfFc1TJP|AFd^e4~Z zAQ8g(l_5L4=cP*a&y)<3wY8z~w?em1dX76Mx^tU2wA%vN?x7hWQdcrw5gB5S1I-$P z`D?s`F~7IYe(y~i6;cj9s7GCRUVY}BaFn}jBe-5mSmWIroaKFk<(RylAaK7jdhL(M z+2#WH?DtH%?_=P<&IBB|55NFAC>ePQ$?`3&6OyGxP_AxxR>At0L4k|0Zq${7vl(;h zqK*O|6kzBs%@5Aj<~Zg@7C7c-mPcoJhZaWwf~BRI#YG?8m~S$R?01TdObOA0*a<5& zP5Jiu>9STMkD4HAo)3dvK7KoG$Rk^WHfe6G>#1Y?JDPR1GEzYa)3vNe-(W_8OK~p{ zZL3;5njYMq?ckEZnqqmi;))S4${M5%-JNTJMg z=rU0Qb6I@Hlv|HZnW@+WfKwTY{3`==Rwp0cHokmkQE8jJutD9U;X`_7#v+Ly$!@%H zs#siPHEt2$5^TR&_tB^EFXVHQC7TE5P|s40mI;f^1w^*A9ls}4K~u1?Ro7@HgM*c=kqmOU3!bo6a;N=;rBf?J0 zgG4!znJ#oDXP1kp^9hEQgK2OVwQn}tZ`gDUMjlaM%Sus@&5G;xXz!>uEITPG*5Xc4 zP+jJ>(G1<*r|i_Cn6i&NYzuP`^dObQP7$5WWl`tL5>R#= zU1V&JZ1;G=fl`=9HPNl%LC$>D;59$eD#?*g+16j-s;En`PCVyCJQ68wSt=db1i7+_ zrJzLOAwa#*WHyI+RW^g24i~Um@>*@u8r{Fy^qL3gAf=2ylAv&kk&Idu8=B!cq9#QP zw*WSn3?s8oUT3pYnH|jOV_m*5Tzn@uYw6H(^krmg#M(f0~BEVv`5^WKiS|L~Zx~2yy(sWKr)US|aw|;HoHTy|P za%Ll(c$<}2>Z8`N-&}oj=h*v|q%?UKY!>2sB)oZKXY>o}&oumR<6Eh5`A3M;Pxtra z%p)~T{J#dgn}RY&v4r(|aFEmmP*+=W6~NMQ7^oi6nqY9h%0wQ1qL+C}DLR%g-GXx5 zS%mfp_)Xn{{?q7k6&#c+qBqQqGoj!$fn3oz#Hq7GI3hD+nmOH{y@AFJD@DQ1aVe`m z$AE1i9(N*g27;vxl;SqZ+*#DQbO*+K-JCqBibUlEqLDi)=H5Yd=fN1AVcs^>d0Kp< z&5ayA--K`J$B)lh&=Z4P&}~y^g9NCrNNUJXHq6AIKO}%uPRe#{9KM5kYi=SvdQV%n zkL~`=JIvr}b~pIirTVa|Xbqdv-~>Ye@Q?1V>yIh^zB?-59M|n1m$z1KIz;}nX6oxs zuc~%vwlA~*MLUR%ZFCW602+L=9d+-3<{EAIk^lDZI=05fhu&{*sy}_X8t=mGU=Wzf zC=`Y$=mrRk<`$zYw~*NIWumCf2htML&cd<1kx>stV2ft+b265LIw-mhF-ZMpi=O01 z_U+p1>-+V-Cd8B*{)*~5iu2!&E$UYMn0}*n#T?j3kn#8M zPwA@)#Exq-RMn;cVujmhD=f6*Xj`&tp@OxI?|4BWdkre(T(|)OJPZQ#y~kN%S$6!wP+7;bKZVVK)RJ%W$;EAHSU$!?K&EmG{8R z(*yHQpj=_VxL44Uj`$9dDQA<^1)EWTh_CbQQqaG1{u57z3ulQMY|RuYZjiYZsb*H! zgs{?-oFojF;Tm!8dL$hLfP-q}#7tsMf0Hni2uFm6ZmPa!{(ClA5G2*TWcPBbLds}GCpg|N3Zw+oI#nGFwH6gRn$(1E?)BPs zS96?RJ*`!FZXLOTdAwxxYy?nJ3Cn3lUZsIuN^m*NQ;3AX64)=f58)n>xFEfG*t|NX z1SW$ z&jgkY#?Bwc(*iHLfJ3JY{X>LAUEeh<>bFQ;Fu(?QaPR`-B->yGo78pMx#0;HdYIXZV-uCm(Chmgg7}a z%z#B5wi(vs$9d@PK(9sp5)@oB2+JNtWC$gvGIGSJG~I%>p+~^)?I6~+>+YZGV-!%N z)x$T*|KM?g9D+I(WrK^xXd%ceZZZIM#^AW{ej8ZHtau`YbHfE}n?hxi7W0c_x~FsA zRJcRD-rnPp{uVegCE5scl?bV3cA^&H+lU%1T;2j|2lmVI=woxP)_V`)&&MV2t~uD2pfKwfQq_k&XB_A|8-@ zxJJCcj-yRKQsWHDZUzbhI;=;R%E#@h67{Xmv(%aW3WN5$46V+;TnZR{aFblA4EMWo zWI1uO-)h+?-Rd^#tE8$E^oGDy#VAb**lfS8=aQV@mS-hR1U2hxl+3g`M3oOZLsCG4 zjfAu-pjX(-=r6xfZuk}|oqE4v#Yr`)WD*3T%-@>SD5K`b4HkzYQ~BV9l??KFfe{p{ z&}RyJ?P?z8j`j_O!ohEgd~b1Rjg(2i3Lt-uU5-`jb=yZVMSmiVsv0PURjQyZ^202w89p>}#f zt~PasC=fVIsdE}8%oyr!7lBQghwj)9svI7)o5xLy5`l;uF-<7Tcr^5KFV9UM4>pUI z8L$)CTUPGzISRmPu>>$K)6|(~J}-ekFEkz=)8-Ew6l4gb)h%Izy23(n2m(2R=L{OZ zS4lnt79r4d94SYSjx@fsmv1jt*-|k)gaSM)qiQCN$xT4|&Tv7S2SQG!cTKQ*v zPzWq6|FK#Yv7V|si$g|-YF?TVm~Iy$>P;wFD2XIq0A1$hph)Aqw=})7f3I>WNJ@PL z;)QCs;F6HYN=6r5cajCXTDgB(;rsS}h4KxA0j=Q2uO($f6h~E2Spj4%XyhqW-fsef zLJ+0qD@DzHh-w^Guwn6z=8V!R(yMM~&BqYG7f;YNX3&arwb-&U4Dql~HsX$$jj7>n zErWN%g;O^ev?Bi62=bH#O)YE#=4pO6 zz(d`2IaK&gu}L*&XpYS2xW3>N{=O#EP4`Wu!?HJXuDyyNjTA-vSqM_wSfQ&{Yyf48w(3+qpJR= zzQ^pES>y ztQdpIq%TRQIyFcl=z4zJzS>{?t7E`SQ(;|Q@$m0tbUTGdx7fXx6%pn9S&MH_f6Yqf zr$vK|SbsUi>Fj3|$VbJbIQrrMgUc9Xk*<^~zztd*-|RLhi^y|G_>Q*y&GS(M+l(p1 zZBvKBDs%r93i%6#IKHtXp7;~Iv0uc)R`TbK`6-@lG}{|>$opCKe&=o7V^a!Rj^X$) zXzf8s`B53ob!g%+SW9Chrb6v^p22rhl^69w`tmuGB=LS8GxeZ70Kti7f1B)wXU{6c z`E13ME^}z`*T1?We}KPrfSn_h1C=%a^3x~2zty!aK-wr{11DPt1_1zY#qA%r1szrY z^%96`V`6RmKVSb@@+JWxXTPBieXME`ihlcsdId^6l@S;oKGl0zf|qvm2-;Vlt8*eK zo(UxKQ96Pdd)$0H4RPSj7-U~y1XIRmKRxIA`H}5-Z~gP*@fz(LI1V*xqB87{ifw*$ zl|yw!`M6O8R9@xFP$D&ynC-$Z&`ol(24Rn1sc+j2h3VSQ1KF&j&n*`QoU&RHT15k&0ovy^8pBr|NSETe6 zYcC7BD5N`ywBO-_m6f-r%%2TTWe#&cVZ@>(S_1q8mIlKs26N$qnhADbch4Y|>n14I zwe^AjTGdXoH8rL+5C+PYt55iMIf_5l0`h}Xqz}KvVQLXU3CQqITE)8aIp`GgCSjIi znBDMGu+qs}J?QkzFXb;S()kOu-H26SfeMjvNw>O%S&7EU(Q)r{2i9LEQ1Ak(9MN}L zBwtYc?%1%8Ac9&jZe^rC6rV8De6k5Hi+Ip=bm2J|(eN+H0dZm>UY@&DaTkFGUBnDW z#pqIdw!`=&i*mnXrsnB&3X|Y<24K&L*+7>Wh#GNxuE1#dU44VUM!B!Zg$#}5BYH&; zG6%&Manl*4?wTG&#DSEpFrzac%l%~qf4FW{Ub?|Sp!R^jXoJ03iKNpxIOHlZAK^M`(O73V0F{9C1=1YV?fWX@c{DeaQLH_A;g0Cw%H( zNHj&_DV3%T^}kQ=PP{;BdU*#}2O=-09LQ+S2iom?id!55?;HWv2fPPdli8kt_xFoQvrn>H#fZ;# ze1SgNYIY=G)!Mg21cNeTA@(o?qeC{}Fb*%Wm^quXjQ)?bk?k$T-(U=of(U=p1x>-| zXnI%aYtV<9Q>vqJW=`zwUmv}mYM-Bbar1aGW#zQ{a3o}J*=cPWHyP7Ws4hkXXSw}I z8;mzisSs{cr(I6BNesVf?2J_1baa&bL1HrHq1lZSH4gJ=Dk;_WO>Kelxb^NOqAh6= zUFQ7OSPFbRbCFi4syu_=bAS>Cw?s5H;|>w@*t)hvi={}F{-mL;2By_v-MIt}zxAHO zaY?)Us^!sRs?^KJGW{_m*?IM@E0apYz0;Zp3sPECN1?(wO^eGu$004OZC;tSy^L=P zxky!8c5J1M%sv#i!+;ip&2yc2`ABV`bo#WisHD9GdM%>q<*Ha0`!~3{Q4Ijcl-MfK z2BK@(2U2kzKGvbo$s+$pwFCl1?ptG^wq?$GfFvM#ePt$|D30wW-Xw`u(D|YP+6r(K z-AoqxeF)RAwtXs-L8)~7$NDD!5`^n+G(W#&zkMC{TPU{g;L)w$a9-#EPL~$R z0|@+f#dumu3^S@@bq>upGEt|{g!2SZ=UY>hkAj~-!`7n<%I~t${S?s1)WJ*KB ziE!2Q%UWXc{Nuyfyh?lhdEIO-6_ExCK({M){KBhia`lx4+`Z`RoSK;xFQdY3ud>29 zE{no1t^xuhpd!!CwpA|kyPp=@8kW8_ozNiKe$-z}{&pX@RKl6h9z0>kwpu^&a5;yj zq|xLmwq2TF2m{X!&8z1`jA|?knAm4NcryDfn?W8yd<@O4#4N)-oSu@J6$PVdO2UX5 z)#%6_b86TtX_cp8#iFy?1a{BHgvg1qzJ+cTAYY(=6a~}KYNc!AU69eXtjlfHu%3R2 zw;7R5&Ux| z7+5w*>g@nY7X>8QTRswY5MRsP<-phcpYs;@PTdoP-|I04IgwMlx!z?AW^(z*AE4d| zzxpxF;0^jz$;R6=P-XX8!gOsP_u+NFl@|SZCg&MP6|a29B*<6?{^8v0qVB z&fNOm$2EQMHS09g$=V?2n_?Jc=b=|* z)xn`O1hL;wyIof&G0>RWuUL^{{-~7{SlCg4)OoL`7zldStxjPiyO%KXegfJ*UM;TE zwvLJENdm0A=l)Nr#FhA){tVhbDuy5NWhUWIOPNPe+enZ{%5bbAWp8sA%V$eFa8 za|sDE+;xOY_21Pl@E6o!Q4d6({G#V>lQO~3{*|4pLbl+>x9}xW^F?{c^RJGPzXW$JjfSc@V9Vwm50*lbz@sQEf6=<*A^k|IhCW6yf2U(@hK8Zcl znfsSabe%&?avZ zJkoY4s8o7fwK|b($44+xlNeT$Rg~y_2LWzyY*X&gFld~Ok#-|J4%QMS0bjV z5fLh{!KVP9g$3e=V&&w;7CZbe!PB9ZyXXY{#kZiD7n$W5F0;!hC&9cMD4oLlCW36R z-;(+&{EM;CYYX%>gs^}qcB%($9(tlJjD8+1bP2D$o7y&uq~qt zCAZwBO_BygXMpvbmMyF3-`2KHG^gq%V4OWlIe4sV3{Qs>J|69u3nDh@!so>ws$pDQMgeOH|BvA36 z##(4drNRNo64(-fh}Ds+E{Z9%?WV3xTV_Xam<*ANKuoDL6Pv}AK=4KkmaH_#Y}bi2 zmiphmUQf_J{L3KF+~G7(Sy2^c>@aAkVYzfv?f;-$lb&~=tsRN|wTFN?qXkG{iJYSi z`3W6;ytc}5!ag23zZPskkeW?4G;OTe(d-qxMYnqU_Ib7`y~vIa!h6WR1!KZNQ?I&J zca#%WeAxV>jyanvnnv27Rd&(&%5?^!PdXxESkdx%wW@j28)*fJ3q#1$Px&s21iFWv zTNUSNC3l$}Zq-S1FnTi^SSHFlalt?_?$(+$;wpjR2fg3d{h9~!$slFNHwhogyNEkW z83mljUoaph+sx7G4P&mZ8;wyc7(%|E43^ajcX0wP5#lk6j@G|UMVYkdNha>pSyRJG zg&|VoGk)*kw6&^jtF|6qRcj0V+$Su}=JJw0LiP4Z0!De&@pE-gGo41t?As*`a}qa1 z?ueS5H~){SWbDt4tJ4C?Ai+1Z^U2<-z4~8!D}Id)YIh2WUJK&{4lw^D@3m`(+6}34fp-I^a&68-@bc+(G+Vu#pEDWmRW9vs6(>XnjPFjbd(2 zrreLme(dmmhmxLoRyOyJ(k#n{e8_xGyk$t1jXmG%gmQN@L&wvF;9x|tk&jOb^>VzW zinA3l=pqHyhiKu2wwRm(MJZi4`Br#s!ONjlTkMt8`LTp5GKhH(o@ z)9e@eT&IqI)Q5SFW%_};`=vwX$+DIHg$?&8e~$%qEMz*rfsjfWK<#J!zb5hjldg*z z7y{dC0LLvQX^PP-ZGqG7P!TL5gwkziBK~MQ(2IkE_LfWca~ znNQ@7Be)*eC88d+x+3D8P){BL)01{`I*kvIC3%nOJOm?Vmx&0DfJR*<_I3;B>)@rZ zfTSb_npgQIPwMP4c15UF<+VhqseY+Rdh(a0{s=5QsV3bnFSxSduLosMNAbWx= z*NPGeKm#p=M|6NXb2yJCF@VD~b{o1+-@K@MQ$jgBaa??*MtE@jH=0ZKMLH>#l$sL& zy~Squrgmy4F$%jcz=wpqRrZ!E2=2J)2hiOyRs_)xa?Wyy2bXuKC62o32Re7m6DDXC z4j)&yl}M7cwd-E|DmR+2doV<01-+9M6>1yN>(IwM3r}QpuO!dUr2n`%mVil~q#oL* zpZC?1=7^!_&M9wcqK^;0<$3xz(~3n>S?l>i?cY*L9K{xV#Gg++TrzB!1wA*yYm%PV zKi;C_@L;j9dS}uyt?-Mh$sh^8HCO{xxoQB&wToBDCa%e|-(jqii^VqXAOnPXt(^6h z8F*NdkLzpm?+7Q5m#?K7!Fw$+gppI>?UQ3=;Y%O4t0!5vqZ?Yn&`FtQuU;0!5Y<6# z45+Ko#8TYXjIlItJ53|sv1O6Idw|zhZ&~YQ(mpcIUSCIP6N1r07A_V%s#j~!J`jdo z5t)_#yjH=B#=tsj6xi9JiKZGyw1!tVKc_?_cc&3eNw(r7cu~j#z(Q>syYvqCpmW*? zYjB~a%T{es)Au+6r?ro*vxINHb@W+z{H(Gl@rL}nQh@*y|DIASCn)nQP9%j?uZiZ1 zLo*edm-z2`sOPTf!x5GI13GTFO?HT|VmMN6iFimpEWQv^gdSZs71q3sqA5ARr^Z4c zk^G{Q8v#LMxaWRTQDZ`R&$Fl*u?@;Vx`dIU@*>Q zg5eL4q!x$`p8glxUbx8Ey?8MXC=cwGy5*|}hY`oX+NOYVV|0Ml7ru~|``~zuL8X;0 zgH(S!KMjCAzvuBvIzT|sg6wi<46flz;>`emIswlcA*7iXC6GDXTdBpUIQ70 zaQ%27=vy_$6dFERk0oq;az}$y>JTV1Dwn4l`5gav_;Wxa=_0P`6+D_zmb2=^&rfY! z)5Q{x@L8h%J?H()-yH@Zk9L9*7%m%ul|SA8cZc!Mj#8qHE%F~k!cy*(p-l_P3(xYk z3xeJ)kG=h6x*hTQ>FG$@KWHJ>%IfudO8XNeyGYNFoCfGl|Ec22?4wOv0n8 zDcfpJ7U5+q<}mFes}LFsPUU@3=*@-YFM}m*u|mNefMveQ3qmYZI?8TLck+LAal= z*DUZFUG$t#`vx24>P#?=^ASD9C74>2_12CpUL9CcFvn-ikn$%33|1?HcbP4aNJoCH z5nIS4h>(nk55oI=*YTupwutf^bY9D4<8$bSR^{8&sfbI#d5vy>9696=?%D_)_oz*q zZY4N5KSy%b9)mbzUV;_yMEkbd`vzr`;ev3<2zJK!{T?!gq5&^nj(o9UX85UlUt_(f``&b}> z#*-CgQE?MgV&Ym2jPWq`9f{NrfdfB<$3UwCCY=KiH4)xo`C$EHdWQ^zY@|YL|88mF zxt(Uoz|iaq49$P1efobd)BZHILe+niWqhkeGB}`##MF;)C52wX%7_+~s{E*QKN|9q zil{PT!Z zq^ocJLP%T?jKeHNrOPP6$DHYXQ_PiCqV5W8w{^#w>_R6M`&F8a$>_EYQCsQ@taNJ5 z^Cm*&Oi~fcYh%Vuj6Y+|LiZNt;8aRm+H!ZDhGdw8Z=Sr|PX z)s~k6l)doRnagiGL*j}P(v*WKdo8jMnPGc`e-j5V8YG2PuoY*lXzvZwnPNOL2fIyB zmk#Dxp1iJdK%bY%B1y2*20^aAVrdFEYBR{fl7wM{wfFX&Q8I{!<(^JT-pvnD6eW*x zo5@E}tu|TvhXa)RJYUR-i2c8BNM)!A@$OhQeMNUJhreb0k+b_u9O@x=eidNg-Pwh) zGU~i~=8aRb%rvUb==Qw$sq1u8hWoyQu{K2);)TYiLw@>*q!anp~ z;CT+-uV>-kTV~X{wkC*bDrT~tdme1z9ca=QJ4^WDQ7j!l6l8|6A@(%$f{l8aVX+UM zMu-xHcS1}#2Vjg&tNWce6Y5Wv2l^E|VI2cjAz3;EIGlxpZ9^krvIIZGgolJaXXV<6gYbI4yc~4%XyV_RxAT?&+W0&} z%P<0jIV6oa!lFlqrydFO4oPBKQrStT<%n0!y}pb3Ylkw#KLr1XU4nFX4^joAq$m6x zz&|+e9cdL<%X30Kz7jR(To-vdWHF-z0UP*}P!ab0N9k}A;Xy<%e7buxnI7!fk0e*4 zCpq(%PX@CvXb&`}K^L64(H=`2cK$1*SFTgCMCiGC{+zP@AH@CBQ17FN#pahEp`5Z% zGymb(28bBK3xKgy2pCKMHw%|{Hng@d`fs&nmh!6XDiFa%3KYHv6&p~#1P|3crS~9jJm$+^2Rzwl`SN6vP!Afi6sOwE1nv02~j5@)*Icb{hx+0=k_1fsb| z)!T5Zc%L{fm%*HKm$F?SOE`u^umy**>lufdu0cN`V6N5H2~--pgV!4;kQ=m^%+jah zryLY9Q{T5v*66qBs(gin6#vR!Dr6=$D%-D<6 z;X~-D)&{F!U1a$`P%qTx5(mgYoE4qraamEs(cr0@^f0cDw2N$z{T}gUmH~74iNuN9 zEyo&T+6mx73nm+Hk?a;Rs&W*plrKvjPG=+bRc{ASc#^FRW*Zcsu*-aiw_ji_HkE20 zgPAN}Bm~(&EynG1GkP!o?2#I}YOr*^D%^^ns>0Hb&kh!tVi5SP!v#$ma`blC3rXBi zERlN$Z{ZXlY5dSjwPbJ3ENdlKf=g)j%?5K5|2)Ci?R$tZS(eppi_?gi#TA&2UaptW z?ji2VMrtFj}^d0P*Smn27N&lZ9kpHnjV&Gum zXbbo!YVW^dw|C?}*$d6tG+|u!M8Pns;P`}UHwCrb{2PMPkx?Wx5f1*rUpP>>>9z06 zKi0AMas?JkWS|erM8v;5p;9NWiHzRbxe=(ekuFHN&edwuuYif?AN7S~BbZSN4kdhPk#9r*3dsSzzvxFM`idga4@cRWUFlq-X3`6d0 z&eqts}Xd>YE zZ|5EkY%V8Eg@Rj2VS`^az!@#otc%xC{SRJ_{}tk8O}p*AH!Mqz-UY#@D!bk7?t$D0 zzi(D_&H|tTHxVzwRfWZcVVyzaiDA{?&m4mVs+vjZOr5_3wLl=9&@JLboXT0X+7p%v zuym4L{C0jYs#YFK>{*tUULSz=eWlxF*a9~mNesn_evcbds?39fS>L=O%9Qr@OYo4Q ztWTw-nLKvan+<2_8^@_F;k)Z=Usmsi$~nMYk*Z&kg3hmaV8QoQVX8L~M5^9yU~f%# zCw0?+AhnFeYGS_WbU-wwY%w~alnsFTBrz8*_;HMEt< z!tgByz7@Fij6E(Zn_52gauGN&Zm0zAw2Pu>oL!55AS?9x=>$wbt+zHck|KP>@w-{o z1z{>*JLPOd8}%Brlg*n<2qaRRd0D%YryXraiOk^)9gXjX*Bp%UQd^n{-WC@q>jp~~ zft&f+&84?n^OGWS*U^|41e?6U^51p0{R}=PTv&& zhF|}m;*kRiN6kR|@N00}ucsAma#;E&Hf1l(^dO?s;>H-4!ATcDaWq2*!n82XYo?vo zqIm;L*VKN@6qVRL95;m%#^5|M_p{hDV&-^?q5RRTw+oLfWXM_am@>GSLUzMV*iM?U z=_m)ff^DWTM*>YRqjC_vm2Lj}o184ohq0yuK9pExTzRn=n4p*koQ!kLJ4^fF2tQ&$ z;HtiLUW~wJy>F3p*1rFS*7(PrSjYL1D?6ZAynq1~^>6y~$G*cq1*2di1uB5(!``Um zIk|B<4x}(YJgi3&#v&^+P}6&EST?3O$X+|scyHvt{rs#jT_KDPMMulSGRZP|I@aOa z(FMZ!ZK5|gNCrj=y$O69h;-h48>EPu>BbC8g(^NH6{DGs55^p1SV{6Sb6;S&DkQ3* zzoky4Cw2CGoQQ9-YvH$-PS9{BTcB;jgL=$IGQKv~xT>ls2C_)?rouz}7Ro8-_d$EX zM@?W~RJcz6gk&`YJzxo{Cr0RfCMFOup&i4fRJ7DJjX2CsViFHnpE60BW@s(VB5>a2 z(C3gyU`EG~zAeP4HGU^JPP%BV^m{V6DLU8IRt0MsEaqf3PcYp-ck2M62e4%cOh#H> z*KKEV$by^t#whChB(AqyTmrxxjKIa{Zq5g3Ng3RHP`r$IN5VLpzmvXD-5b&sFq!nG zn(9N;OXGm5j4}BdK++NST|j6_!7`wz)`YVeZ3rONsSlB&%;1LI$PnH^{oPfXmwY)p z0iKL6@RYFsc0vNCK%R#G#lXdA+>rh-aQbY%q;$m|&9Lc+VjIl9;Ik^y48f1|FTUTu zO0KyvrhNa*aW;wR+i*Bv|M>!1>Mt&BqsJ7}975vE|`o>bmQoY|jr z=P|(0HYe3*WR<0+%cF%%e{L1U`pZ=D4T;%wvPqO9@LV&E8Z72Lj-NY!R)#V3@rkH4 zLoBe~MJg{m(pz4)=_YPVWmua10#~@xJ#h@ZInYSWn7O**zV1jIlN~Fbgh(xTgY?f!QnEdfl_6P0ss(Nvjz?4%*3};* z-y89tLEcIZwf*V+6Wy&Mcc2G{Z?CR% zIljNhTUFKBbaR&?T~FnunM}EHmQlKoL}%#Fv%Z}&s19(#`B{uYSwMf*6Byjlkx)2z zG;9Lm2b+WT&K0+Zx{F!K{0j-Rf~j?aFo2i+k$>AV0bHXOy{R@KF$OUZG+gg5CH%)0 zOZ2ERD=`1Yh6PfN{Y?q~&z$71H~;wbM+^L4#rCQziV5O}cLwX~ARQfDq7;lOU20K- z1r=p8y40#UBPwa3I(;wua$@BOU}=5IO`zk)JIr%G*K>CSf?IS0F4vG4zSkwe5r}$f zdf{n*E(i>)j%q{xaMfgs0kVB<(^F_8HI0h^%Bn4HBkllj2o1F0@Q!Z>1v<1x(MK!E z{dL)AkmgjQ%$Xzp=WW3>Q`{!|H?wGySJfHCvqy8S($pkVak?|MM`8W|I&$W0Bh82* z37?rJZMo(Su1>LYdK2Dhv!*GgUoGOx(7t zc^Jn;-dTQ@({)Cwow$ZGTJq#yyL1ZG@`@ZVCPRlXC0HhvrsaX%@9N(u1%fP@jz#!d zHB!9gI-xbJZay}=^Wsntnll1k0&eqDPu`|*aGHW+`#nS1mb?m`)#VcZ!zF^}4 z?7T|iILG>PF@|H(`O<4gWf#QL*8+4-Zi6|g;a+O=gkU|{Iwm$bMOiz-NU)!43$}&e zz)@Ayv8@xq2H(}b)+(q44DF9{64&f3|H`D!2+9=jZ?37x?e!;MdQ9!1cvK#+fy>#7 z4xYRU3TC)M3uveALKnAYX7{^qd#Q(!f{4*DH#ZOEzNLPnM?u@Mu=Qn^JXYQzr$fL# zK8rHaU~}mBKt7raNc$0V5PKxP;S$bGRQy@gy))@k`^mhr7s)5TF&Nh48&uKVbgMhD zPWDMQ4;$S(MTv>4&EVvOkCwSNO&bJ9&neCOQ%!^X`9|$MG3fV}Ssr;I+fp3fd?}ad z2C#I)QdB*$x&~?TlCCZFGT(UgTrFKY?|L0t6xNVcqn_9E17$J&Nk^*?-RHscJUKhC z_S!F=N8Y;Rl0@%-j+t*iC2y_ZoCvtx@S!d?EfUS7v*}1@V>o+2@}Nm^iX64P(Bj-4 zghC(vLLdje5C}eLXmWq6fsp|_khn(n?XCq^n+hw(Bn5Po#SoEx3aiy4V!E#okMs@+ z<&pL!FU$TQ=4)!h{Q%FUSds|22GdG2bOJwqFefO9ir?>#$h*SD|`zbZ2TUX}la8;v|dVb=}}VlXZR3O|og zj43Dp46Thz0$0`)SIj<@QP$U;?#-lwC`Vvx{-F37Pa@<{+JT+suwsAl33637aL}X8 zx7(-dP@~7CYwHGIGi{)w>WeFy{YTDMvi0#>mN<+f3pz{W3^%fCz!sP`mOF#{-V8zE zmn}2#X9X8|h@o8)s*b`p${cy}OeucW{UNSzKQjp``l+_<03BwNF_?D*!2`Xm*_j0G z45yCgL=FUW%&%Xucb@=ad=Z`UChMr0Go20D57REPJM0t4K7PrLog>#)Dicc4^VA27 zmVUD7K>0!I^K5IH6ng z7Ap}n09i)%9sDmh{0F8+I``PIG%(P~0R!FNd%P9^U@ibKux1c(0}dVk%>_GH0DrJB z_|pSNDU8bY0-Bo+EJ7-u(M(j@~DFd;Pv|eS_#tbf@Y|CXCeu@grQ{vesV^jCN=DX2K$PR^)!Ub-d_oi!dZCl2YiJZ#o z;xm4`c)<`cMo46x-=qU2w>PAIeGZ;2;@GcVgKNQVMF^<9OqjS9_N0wiPPAm2t4c<^ zC2iwOXdvigy&kBX(?$4Tbjn4;P1M(1a;%Ag7CQO3FyPbx(G4o6v*TC>#mw|V@%YRX ztx)hg|0ifFyYO`0MBx}^ispsB?XrDZ9v6XOR9?a&`vMitoSMTmUTy@o_s07-gPta; z0P68YXfa3SsvqTW$}B1Wa|8_Ee6tsZ`?l6PEHgrA^4UOw>0rG!N#>T+O z$lS!ikpYMcYwP;QUZ;$SqoaYDiOior{CPxKDq4=K%82jLhK5oKYluTtO6dy?+Fla* zMHoNApc0FyFpB+FP=fgxhku1tM$(y)rjfsr;~&BXLX9!6M#AiUTBnr~fVWcP7C3tgNYH&Na9 z82ma*hwsy2_}vpbV^y^0usY+Z`J;bYQ^)gC`q9>LXqxA>FtOKdVy91Gqme^G-L{c8 ziz9xIOjSf&^}fYu$uy%G0#>SaQ|97)itS7C0dM`o_4lnAW3yn5oSNCW2ltEAT=i&1 zNVW>KY(+eoln1QqFnCy{g2-nza*_@hlHS##9fL?j02xVUbqVQA_C=nPGerqo->-tB zG}fB0^IBG69qhWr3L{klqZm1`bc?4qIsEA;{RE}0!3G&#EvA{Gb5X)uY-H;8I^ zyJ41Hy56986OkT!oKy(Fxo^KGpK3%8OV^IPpoB*S0PcZMDNO#3zeCQIa2dcJ&vO zjFcw49s0ybqi;!($}ghXTTL|6fT0mdXe<;+yV3VzL|mUMZvP-ac`ejipaI9|o4e-k zYwym#6M^f;kRzBasC|HZ)FJp%jvox=Q+rWNMh#%v7+ko35EY0M-6hnMfV!{NEg=ay#=9G>B4(Zo0tFc z0aK>5u)b5Ws{LrYnjH^n2?Ry8 zbi8(#V?O?E8vKWI$S|%;bO1uwyZ~oZe+MuAFV4Z;&g2h(#J@R(Bo!Sc92HETWb3*b z>nB*$5-KVY9ArsKO>2;RI{66l2KKm>h3dY2QVsIuf%Pbohj*wBhfMpHD?zhFX6^u2 z?j459g|CL*ljrIC5fiK!o)hk0a~`~N9IqFD{>%n#2@VgZ+)zatMn|1Q@El(FI(#MgxuLS&saCExK$BSSR;x(D|7>7CX|4?RW?6dR@_9{snmuS;pnT z<$<#Ac+ChZPB?edb^&(Qjs;mTtQ8OHx=p3vFhHYkclBf-%)C5pz9t#XfS{bWjO&kn{kYMxT(D%`$W50;g23btnvn1GBJ zCds0$ivo5?7$10I6_h0FK8Rx!H1Cwb19)b4Trn%!uWYNl;R#OlXt1e)Atqe5zVyyG zi7b+oCzz92EV;t#V+y5i2I3p0S*aJzYW{Z2QGu%_EHe0lz}6AI$Qo&oxik~4+9QUI zzf~)`=qb^I0!Ofe0-j^}2#at;&fEOE!Dcks&M_-Y+98vJ~SFJ!E539<3tGQsvZcvT-88KS#a_o~#?Q zvxQ$iw8U`>kaGB@L;XLjy<>Ex?Ups1ifyxE+qP}nwpC%pb}F`QR&29k+h%>c&U3oY z)2I9G@s4+Ve=^3-{@1Gf>y5Q0 zm)E2GDW>mgfw})D_`|HkZ)J-f_Bh>Op)JdMq+$+JIm8UnU$ludLDcwjum>?H)zpP> z%Ct}ugpwnnP=?TVsFYHA>Xzh*E^sh`V?(KZSpk_+FL0(}G(t-@~E4Ksp^mOlvP7-S8;MW@X)6_hl}e2@<&TqAqCL zE0xhPYmvNJFGQ2l1MX?!O-P*k=;_$aPO`b!vtT-9wKtqIitR53b=EBq0Q`?()5(~8 z!hVue5r3B*49NaKQPmF4O-Vz?5AKjjdgr42?|{oB-ci+L`|sC@WIaR$dcB{ajLrqT4xD z_yKD5gl^oCRwz3cC@c+p4^9}4M^VVGsV0C*LlqA0C-_^2AR`?;lxcp{*`SESa^`PhTb59%genw%`A^e!s9|+SU?linjqAZ>DL1p5%qww0_fZGCF%`6+(W6XJWgJ& z&Hl!ivk?ZJvlI&KcUXG)eBCCj-CMuBA*(LLm0 zDifZ%#vZ8z=qg67#dAsxwkK{Q`r|5yeeZ55nWP6p=q=R(Omp1=INZt$9ac@3RQ;`^ES& zxkdeJ?M1kRfN~csk}^O^$wlKGQegazry$mO=M=OuAo=EoIFOQyvk z-OPljMTBBTo@n{KZu$hRrX_2pcevr$=13%Zc7ViE{ybA`Y>3_iqA73~&`f1yxr`#)h&ybIA2HxH(O)w@w(h1@+fZVN&6a5h`j?{$mhO~Xtk)USwRZFSG-AVUL+%YL zc>+v$=PoOUZZbj33|;A`Q24~!gkzN&mdA&j2P^%S_C>YnI{niNK5gxi;d$42rY=_~ zn4=?-Ki9JlX*`Zj6k-fXyy>GQi^NG4cqgs-knV@96oI=F*q^E0kbU3T0! zrs$~xgumQ-%!ruCNwzdfjE4(Tw9Eg~;XzH>qLNfPGYySS^MYa7VS>$i6}fs4*e0R_ zWV(@dhi9IXH?EdrRkIH)r+#hWN#nPK5Fjwg=mhLw_I^b`h;LS~54Eh|HF?D0>IG@X zb{-9rIGB4B(wv&=w${AE#H*dUURIaA%$=;s#*5+fRIclRp$+zkR;%871{h}gdx|;y zO7%&`{_mA~8~G!pb#`!TX?C79&V2Dji$k|xZxh#>2`DcP$q0iuHP5oqQt60s%hi00 zPpH>7tLYGg`&74m&I?0ty$Vc%yN7=?PPFm4%_CvF)~tgDcD_i>v8xcreIR z0}}EGa2YD*jlO4FOenV&AEl|WZ&K{-rQhj`!@EC{@Ps`WB|bd)q&{jBo3&5C4DCx< zTK3WC%bjjG$drnk@6AU){rqhNbjq%$X!r$-v(QqutoVTkAk@Z87H@On#*+EJ_T+D% zR?o0Yqu&#WenNLq%-C%{4FRKnsk<%c;dXOp{Ug+tEnM-gW>fdDzu0v%vAYH)MG;UP zY!T*c<7o|1qC)xwNYIE;>SbN_?j9@;;ow4-yA$?vz$53~<>=UN68+&Lq zP(dz;(L=t0={pWO1 zNmBEV#y<$xfgN7-~{tV*yZBZ@&8HlpU`lrFBG{!xG0w6lo z5dUr#`JY5b(81gSL{g_fh4Om#Xp?bYu55N0Gu>rVWvK?&2P zF_@vX)c-89>WBT)98ji#laQBXnA(aqd)-QW3cDW+_@Orv+nwaPu4M;`zzV@zdlnm0V5a|KcTNmQ){FZ6uj-|kCVe@&4=2HHa~CQe+W#6s@BQ&97+X@ z|K`lL9Z{XHHo0r2r=FXfJWv@>J@GUPp}Rp(gxhL5hn^O0NU?o!23AkJ7^ck-jV*en z*=;`KvZKrSg$zl@NjembMx$N52=7+ZVR@;>nxJg5NM>EKt^)qS8|i8a9iu&Zxg3_! zlvZk;xpg8S>kNIBhRwDxleM}F8E1YL!{Sd(x zT~x5pX^9~LD9~&cRT_H5W}RvIq)I2v_WC4-kKjtF`h9ZPBt2D#vAo>MVGh0il z;P3)mgT!ddJ7(W)_K$tS?EAKFAX?3~$*Q3WKc8e_qJFqm0P9M+I`Wpwr(*LmEdHv< z^sc_EvZ~W5FG!KS*N9&A(liK@oiS7ZeU+0(5nSc{)jjCr>#mRf@nek41Rq~26MY_U zsJN?4>$T5{Egm}ibY17hjW%+Hxr_B>VA+SqB=MIP$41{IESk)nmZHHr ziv;c;Jdwiz@K!RnT{uT)iy?|S180b7xu^obj*AUR^p-D7rp@$Ef~N@*jJCkj2c?Gg;Z?#En_q8sg99c%=QVsD#yt_t-*Fm_dN8fiY0VVvhv7 zfqoXV`;H8Olvbgu>RSTBT=%POT_PA(iAkAD1vT@!du7m$AwR)=NM2ozFEOWkB(G_m zz}Qx41R#Sh3NgVM0cJD&mIZTl`d`P!ykKBAKaj=5e9st~nQxp=3m9T&$ZNS=gVTBu zTKgtF1GZ-M#eL{d2xgWFjpF!fD0hZ?evb@%Z{*S)cEBU{(jN}ViE+*rq+hlaX^o5z zLqcZw$#ax`0v$k%Q}=9gPpd%- zyrwE$*33QfEbA`(Sdmnn&0&v5_XDBU-d;qKFAT5f$25mPkBRYtug0^7d zBo6Kh1_ve^x-f#+JdG8x!N|YcKA8pL+ry6z@*7N5Un}OI$4Fu!DhuBh&z%DER(Bl9 zK2TEM3qdvJ;{fZ`f&3CAz#Xvh!;z|NLx6j=mWEqr6G1R?y@d!X<&mb#>| z?pQ^lgICxHs!FU;Q_C4+PLH%5w0h_D$0Mi}sS?$ap*A&ff(}bOSLngs3v9a*J{!4}(o2B}f zwnE0@mXVakK@!`duNb9ISx-(=&UVwD=ARE+?wb(Tl0FMDo-`zm5S1jC1H0f;_|o~oq^k|44kj9M zyV4G(hocknxj%wqMB!q`@1lPENM7aS^b@4)UZ*+GNDVDZuKM__qZX!cJU zsPL$=eLrv<$*J`F(JPPqTqd-JwR|h9p6Bi~>#;tfDR=p0x&Fg)i#58)7ZtO~W}HB| zm6iFV^{#D@DV0+yt=cxpNnFl>(|77_vdA}x9L)$Ki&Htzdai!b_@GF0K8w*zdR>c(--p^jX+x6m^fBY%_u;bL`%D$^~ebD(D5 z>h8Q+W8aXG-eAMzF}nE_Tcch#p9%{!Sj7?1gn%NEy7J~X@FpVX7tb|E(rmc9Vt=aq z(Ugg8Y!OTSlmp*lMslaRiTGZQiwO;Bmg*P^7f_;jtR-GE>=3XJ^fR`QY{ces?INA* zV%V+tdh5ZA`yp^BLU8%y!QY*Ihspa~EjpwtT@@~3_6%UgpvyR2s+e7~h2P1fLwR$I zooKxI_Gjy@(W@HfAeXb$6WMn-1QR+Y6Y9>>ofFzV^e6`4O{FqV$V#&6wHe51pgfd~ z*Ur&SK>dpMjyS6K?4rgU6o*S3)PZ|uZlpV__rCbZ4a348rws;x(w#h``Bm+umFf(M zF8MUgf|ypIso1@Dt%j7V23cSMA zQ{g4M1@DNLFex;9?M5Fz!+d7+W^!!`i4LoFR@I(`j^*}hKOpf)dBM%?vpSyPVQr#q zeTG6K#B5CyX3gGYw_?h}oJmqR$->Q*g@3$^q9}had3$>s6vg+l)WXe+#OptCNghW2 z{nnb$yXE>0LU8;{&Zn~S!l<6nxBcpPolri0squSC*}`?qrHhqVWvzZlba0|Rv7{VN z((uv5J<$%3ZhK%lpPg87zpVwA1IeMi`_M!_AAGlLD_R*}{Fp^m=FKF_xv>VTSS<^# z(8I5!dN9PD=X?Nfk2UQ@1j|m};_Ih7U3q-ElT{Dgxg#Z|+cR!LgSp9=!d~l~A$DO< z$JDvk5NRgpb8djyS8{4wHu{Sd+Jzb4B$>!2`NS#Dz)P=A2b$RY1fI*3^P>ES@YgDC z+KnYEMz=dTuKVb<@L=b~ODOHNb_KjHIpx)gQQ>?h=RzUP@savS)_b4<{62I*W7=-C z6T+}}8&??m=eNi68CHrxH(Sr3ar7wA96YDS@M+XX$7W8{K?*)RpOi*p*c`$*pcwIQ zC`blO-e^^;c`b+)d#?G(#t25ogW&m#jESrhCfmgZm%{;&&KX`ri&=Q(U-bktc~F=v zT(40L1FB{mn7L}B&o1D=+pWRdvF`GEdkwidxM1vwnjP)IaOA3{g!pp+8TBF4yWKzx zvBa`p5ZFO*x1FD`zdac<0r9klet$9hChP-Wb7mOXNl4VIV*nHevz?5sqFw7L4pi_R z7)eHElPms~#=R&BgvO~}sN~rL<26WqHvY6Sm%5`NFxp40wk&YL3V1yl^ra+B{X`_OhUVd8XhRI~{Gx2qWJ1J~uU`KM|U(q`dzZvn5fwFIl zsaC6PsCDd9S3%Qzf``=-UGpAcYy>e@=-Ivj=8T?MVyt%*gsWTgaY2T!Ia1CBdT-WH zh+pj1m3~%LNVAXfaY&`$!Qje$|HgXs2wGiX@F|c|8PY9wOy!e?*!Pg_-lZl z47hNDqKK;dy;+VMwkRnQlIUxxt)SJHFSLP0MPbbcc@joG48mArafYEXB_hwj{1BvH zk_Mg&!wF2+o%3K*0~3;*ANkHIyB-fFuK2gQK_qf}38(g;ka;o~tuBW=qV_0b5y^n} zGi^ZoQDe_TR-^+r>q9d@Z_@vI)XA~Nvf!c z7Z?vx1E3&aEkyL%uw~p$$Pq&MrmnokASiGz&A$9{srE(DvRi}}F|bDB zi5YK@I6@b-l%hr1a%kD-kTye${L5k$Rb~<4C6&+{>dlyO;-%c125pu7?2(5(3@&RO zspUc4i1VsAxzL@kw|rU_&j*V1ptCikWGYwtugI>G;d-o*8l37tcbt!#Ky`QW9h)}h zg;Tf%Iecz}uO@8UF{`##x)r8P@f&_qk3<3}PMMEc`!6CD-v* z{uTEP>=LH+V@3TL3m$2urVfmD)p;N#lt2^17*lb;qNxaO_QXL}t8WgdG3T4Vd+-p5 z1399_vdgzz%H*~}w%K2@9mD9eka`;XaQm#k-%+Que*Z1;{SPCB7Kq)M0x(fnz=jja z|IA3G08t|@%AU?HrnY}oo2(olQ&|!A$3j!ZLr^x29#2auACEV(hyrM&w70*m@M!&; zo@XZ7hc*8Dmrn{tZ3I@tJ`=Nqm(Q1XV29{C+!D?yNUWa7Uuk?3YQeL3_9&@(FUbxs zb70A#>T_f5hii!WUdfnReS^veTb&iYE3I8ep$>@L-0bO7pP7B{n0?00(dtr3=c}l zI3Q{f@IMlH8H0 zj0F))xl~~Gb%}12j3#(v%545s{gwVWDv+Kz#_vI#GuyerW-bnqY43Yi$I~XjK<;DW z^X}@(A2=9J*WeI!j={jfdJky;^{2uNCa0)6&JEsb&1J z7(ovel3o*32R$vkg^<0Cd?ww=saSx;c=gci#be9GJ^F(v>)!lFrQ$lXPUO?4VTrpz zGN@QfM4N8Ieo=&2Sz`iows(r|hz99&Bi9M6>$(OkXO~#%!hrJ>Dp)W}hBh99~`LsOPiNd`y(AHya z7D7E)TZ#>w&Ut+#SC$J+LMOm(*ttwVLZ*nNc$(O+$CBtwPQ6q@HwG&*BblqzB!$X& z*UABG3r1kul6zy>OnpMKK!|_xmWiBUxtT}QVwkc+liAjh9zh>;(m{oFy;r;QJ=YME z7`vFUU|Sqc{zfJ!z#Mo_z1-|@WjRhXhHS``we+WmUIF1-Kxk}jZf@=lP)evUE>slD znDH1zIxL+P$Ke6|E_A6b+h>m=F=7S!>e46S1H{YhNLqRDrkNZl*Y1y{QR-AU&EyuH z#KmIE!&G>gh)a#2_KGa~_vw#403XCGFN7hg3XTW3Nn8`v5EDh_ z?O@~dC#00&uXX?%Gtv8)i_^#ZOC%zqQJL7Tkf#ypd@ak74RnpU^dF_DH z+sU$nj%4XlKJQ7Ido&dFG8F8AIyV-ZM?HOT_%tK?_t&P%H{jGSsBt7*Q;Uq8b z^zCM(1c!v*sAfjkZ$QrRB6#IZ{*Yt8~0l=?B@G5bv(!#WZ6`1lex z>WN4R5K|gZh%mZ;b}T4H-iy)P{@XOpFxXGA9)R}l0Fw1zeRck`-Tq$?j^*zF$M{da z1&DV<5k%#kPpQ@w*H(cBmP#I6n5#t`50dh*{5rdkpmLE&KYyW6tZAP3ar~R${s z#B%|xdvj)ExO~Ux)NtET_8TL!|L={>FC2ZD&}y}|p%`pq9+t)^-00ZodYT@Sz2<%H zE{GMkq~A%{5KY2F&E$G(7Q1OvELar_eKVs_E))P2X_mgD~HmJr2mLAe6nfj?e*|23cMzr6#0k!>Y=Lla9= z5~jZ%f3(t?R6pQ;_nO%Jkjnhhn=a)_BqrL8=uHs?q-tJDez)yf(FQVmT;XTxHki*f z$UB828|}g{I1{(G+0<%7`un?|F9=7U9X7Zcjf~-9AM%n+W#$S)-6#XzgNX)*3B}@j z?%5-RlvuS&JS6JVxlazLj65w=BLy7ePR1^AHgDn-+{3FMeoX+*sjvbU+wK0u^fHuWznb2!YM5 z30nMbJrB9l`W%`aDz?)IMW^tQWX(1<#PoK71iF$r;%``?(JTpRvD_H7 z5*32{yG%dQL>t83-@p8er~G+>VZxepIsl$B4VbA@|L;8I4DJ&P!_?J{g->+;m9L9UcQ878E zQT$^!Z5`6}aUzmtmtEeMoYS7(Q;(A3^7vN3BC%enMu3lG`PeY4ex}t>0k9e8gRA7+bSi?UL!@5z4Zd0d? zg4W`&+@Y-!=Mb_-x9l>k$e>++Ze`eGhi&(hLDw+TtIA+mR(VA(&!U=$zHPYl1uUcFsbc+oxWW}XdJ~!$~v1MC3U>h(n0I@lkDC`vfjCui|toQUz*o@ju zEk}JI&z?Og3ubYzRV!S!!0Oa)!|eLSExO1@LTR(1%HA|PlHH;VYYIde)DJbvl-p`7 z3OOHbP(qy6?0}dv@0%&1UAXR2C>kD5Ow}cJlhEk(ZMHoMovVFY6sO?GF5R2pK&v5N zLpIYzyu#DTw1}#EiErPRHxYnowYM&7(0}q(h#(OMk^AEFID|5CIh^ebt!5OG_aZD!>)NmFslOg&M!m-hU;^uV@gDj(P&6}H^0-^lUnMX6z*`&cp#cp_0}fNwM8zJ{J&^Afz3U%Kc8>gpRuyzS6-9AD<<%*{dICFS>c=!c`HC`T$mEbydwU>d{wu2E%3;1;k$J^ z0m8aD?>J{Qr`C(+7W@x{BW#I7oMUT&w`yrH458!xNpQINH`6P>?$wlv^`b)kd^hWn zCI#WwOb4(|yoH0V+!o(TX&1;3xWX7YL7I@SufUM}&Njs8bKVt&G^}JVhUFSM<4aF+*oQg7DiZCbML^{Wu>FWTO_V^5lF?fSXnY2Fb zKplMtK2Q*n3HYQgzuwUQ3#9(^ayIY{|1s5<#`yQ}-~V>ZIJw%GI@2ow{>qx#8ksu% zr=QWk_8XQ4b}B{!sLBH^ndCg5u5^Y1wE>!96c`+`>aez-DS)xxj*0nf zwpe`bzCOGS!8s$bu&HV(4AbdBBKCfYe}>>y`FD8nm9M#*`--mC`Xs`H_g(kaljqSB zxBbgL|CJw5E?f?k#c<2Z4W8e6O)dxmMd`46>g-w2gl}xy74s9wpw*ZWqxw)&tl$N4a6jroFM*-0kk#J<e%GM$$= zG}ep4UWY1LbEe|-q2NOjTBzI2BtyC5fyJvxQE{;t*e&ccUMBSDhbIy7@1dkF0|`qe z`xaD&97x1F$7({%#A*>J#we_=+jnGE}uid5xwE*e4` zbuO61F-4rOg+CW-9OIQ@48@E>FXDb_#FSQ)>afyMgkP7`kAID_gPJ|u{n}OT7CJ-% z`bbJn!A@-?CPW}})2(Q$+;}^t|e7AR(=845p;ibe% zC#b(ghn=S6IJ$4&@j#VUT1Vnj;a!Vq8J$~5+uZU|lR~q;cuD2HqZk@*9l5*t_@ahU zZ~#$#Pa3~Y(y5D~E;DjFI(wXUj*w_|{l=b~lF;;#V?wFJe73o<^kY<_D;lRoSjp8= z)W$@#m$7r{LPNu^8$R-v;|PF>@|of?E$s9(ptCh=^Nnbdtn^wCu zS=%*j?UVH^i-&Nx#@8*kTWwYK_513UTeGP=rUV&H$1cEOxCn+^AAMIt1g0G_7;F5F zIpj-4w~L4N(JDDeI#G_0u4MyYDL>iyPP8`_M|dt4(mR8tlYf&@<+nfY~2K1+k@ssY<&EDvdWG3yWkLk zs!t&Q>`Z(Iy|xLO5e)RwlJ; z<@-?VsbdBtSHx{aG=%C?LPx+=tF~0u8Z>OXB}m7Lz-Z8p;a1c7SQrF9LF&id&CqXd zyBSe2=QQ@{GZc^$MjvNqJz(hVk)R zN{(Aw+K6y-0gYH}*8n-f`Sd`yZxuj*RvmbPBioU6!!ml6l!=X47LShD2S@?xh6IaL zj#v|G_F-onGFbai!4WE1=0LjCsQnx{2}bBa&8u1njMrZ58|?Lpiagm;d;nt|*|%%7 z9e8K`K|&_t*?lLRs=79L#Tco4-W_)ZSMWtz5o&X!AA&Q+i$AN>YjyfA1%$Vu%SnF- zf;dx?_ylKcpzVZ4(55<0PMH5Tsu>GWZ`wx~S<+gM(QO5Ja}e=ZkZpETJhda&1Y;{e za-V~~x)}9X4XRN@CixkyoIeIh8qDE%koMJ*SP?@QqwcP{gQ+#YkXha(PWu0lBIjLB=BHB;7Nn{|aZuz<*+F^d_$7J~%BFiWn!by(yB-ljQq*~H0_NHjAZrr*iWBz}W zDt}nOq{Yd>AwXza0rvZ3{&%VJe`En9e+d{_S7#R?Q^7wp;h(%fRr^mP;4a$#DVRbPa-s~d=rY-*a1mZ30m>Dr`pvaWVi!kf(}Ot5GnEf*>g zuFKj;2sw+3B`YXv=uV5f)zpx;t=l8zb;>0gp_bl4zt)*dbPaWW(e7Z6Ra2;pAh%3F zKjCsZ4mrjU)-ONH2u6gX*u{1(QhazMM$%<|;(-s)C z>&clCRKFO6nhg74<*zs6#d;STD^pGl5Y`Aui7O%PUNB;b$)`SCTq;^JXEBYGq>N7c zc(41s1|O#7-PDpECT*DD6um=LOxm8DQ?v2ayZ2ZATCb2MdaB-Qr*_j-Jd0DWFEq^l z0^26yPRq8p<+ClDVpyk*umFl9r<*dEQFdu}j=RvfOCU^OgpF?0`du{iT)&@nvWz5F z1p#Rgdoqv?Y$K@3S)1LuBc&9oLy>*Mvxr?xjBy69%pQ7K`L+?dt8m8;mUAC#>2sPx zNuFS9{Bw$2e|D1LPYAodDdDXcVFpRu8#O*yGK;I4Hk?$tjEw{apG^ws0`NAk6kl z0D*!hZZ6*Q7dU=LtRA1~vQXFgh6FLk(O9+^Mj zer7!{hncJCcMr=Y^gvD?h@zln6kZ1jVq*O&p!15s4D%3pns6$42wh;(VqC$x;9 ziDk&b7K3HjutB2pY}wBb^@PnHt3=}tvtv>w8Qpuz3>%HS!Yk`@A@q!N7b!cZ(-yW2$*rbpZAW3@^Anz- zBYJz>lp6Bc+r0S|%J3=OR2~~E?gJIz;@z$NPpx*29mUkDvO!nrDS9lr^z?TK%V=qY zi;VQbNo&n}j?*qSp?S)VVT*`;NGc&7-ZG)H{4j53Po=HOSyU8-sbS{Tymo}neNL4M z{wrLEMO!ABg?&&pRkl0l(yY*{9hOTrmoI?>T^FsFO1kQETnoummTBfd!hEZ`u?Gt| z2%S?*G%n);vb87A`9_r)w_DL!0U7bH#>c`k{TrC*wIgvK)~&_d$@}r=gxHf@aKiaE zByNiw6B0w-*td+R4*DRnw|l=?EElSWNw+AjFwtTS+o+u^tv4`V*DmiiRI@T#`pF8} z`(0w|^HVQL`-k{@aK~y-pysQ~;IW?@3@H(7)Ud=71|OLYi@vSeC5WZyJ?dFKQt;jj zf|_v8R^`}nU9dnLOA-({2z&r#*H##3C^ahnJ4xj7@94vaWHi0JO^F; zl_?iF>UR-c%e?Alh2>*wAyWrlcqzyH^j)Hh0JDhmb5wDhZz*y3>jSh0Y-QJyuoYhc zmeKISF71NcyGZ30@Xk9U_Wu&1f2N#BaUh}ifRZT%bj;ZPZ%XE$@)Hm&FY4sSa7OLA-JL}z`0G5nm-@Zpf9OT`30SKLv> zfCck{DEB;IV1#44dgrUjAI?|l%x`zgEd)T1bv=QAOQmNkfqBL_@-94s%y`rzRvy)a z!-y49;;#^}B`+BjTxRUK3n~G7kO%3!PdY1yb-TPbKUJ@`BJ`9?rK-m1+bo?+jPZZ1 z-;u$j)D+H_p`OCd3JWolSkEDm3x@IR$!1cWWUTGFWl>`tvJaN}6Y_>)peRcWV~p)Q zTHvCs^f8~UryQ-PRy!9aRZx-Svtvf()z*T8XK}}zA$VXenXw>MU^K4XQvGljBM?5c zMhIRZ$c^<{@@BNl_Wx+Dgg|{{$jR3&;2n{6mw~qHl|uDpWlU(v*?Fm6S0r<<7+cVo-+~LOCL{zGYmd`kXW#A`2q{VMrm_} z*Sfh!a?ZtVHJdL@a$AGV)KBzNhTK_yJie$+4i}mobkiI};Uu+!G_$%Z4J(ncl?-@#jq0lxWcNaTCC_^+X~5o(lF@Hef|``@+%psg2(qvkXS&e;HAV}0fn zQ|l^9t<1k%Q>~sT^!j|uc`-#0{@vrPYQ45;;QvpM23+xUu{8bggY z%fX8XdYhpk?H2!lG)Y~d*cL$gX|7*pbZF0PZAVUj!swA$<?Fn$hB6wmGs{*LFlk_k6BF`@wUuLu*xJ=zvG#T5$<~LDQ(pZnNq^H+8WbGY)M0 z#-Y1XZ9LDAw}g5Lu8ptkb$i=Ugt{zzL2bW#o1JqB0HSGy`&(?f3_QCD{|V8o0Ep&} zp&o35?;k1S7_frgfi4&7WG3r>9_{P)_0?EUw?o^XM+h^|W47aSo^UEu@L%A=p0jTk zR{Bg^UTSuDD#Q*sr`KZr{st$wyXmUyURF(Rse3Aw%tFKT?We@!k}>DQ5r};~taKNX z0t)IzXla^t%DX#?dG2B1Cnh-JyaRhEDGjs9Z|v#=IZ{s=?=Z0jx&AncOep#2{vRft zB|pOmW8bhX5^~;4Bn#Ylsb0)Hhby09sd?k2wcmyqRvh)ut4b=e5Juu|O9(7Igc zvDkjU94Lh5)68yDg5@L_YZ*f`xijl#{C7*(ga^?<-Y@k z{sqwU*w(=R1ZYM8KtKHf==7JcZHTBzKS2OMn>Ahm?Z^WFy1NDd(1d>j=*&%806-4| z02&$P4?vUr4WJ9En-%{B(B;XyOcw!ae*k(o^ILNq-#-Al>RK|Y{2u^K{GR{~vorFy zq2wPQ(9$Wo^&1DszB3}fqY)A$!V;cA-W7+K=jYJJKrB6v(^r`Rq9F5b z^8E3tXUiW5mIy@*Wk|>b-!XBQ3@4#*^Fd@DOh#B5qEKx zHZhO2q1vu2A}^z2EGiEjnlf}Iq1&oF#@ZUTpL}^foJt-{GM!3Z86}v|)?z9nnVzBc zNySN;^}h2|gN-v^6li%Cj}{Z>RWn7IqSB*?5|{C6#aO%b zX-s}*j9F%R-U{6`<&1>|SO^H66%KWVZs-NKng}9Xp=)27!ExAPV?O`*c4pn}!uOOY zJ1csfm`62T#*z7yI;Zk@Rog87)E|82vgFNkjTJba(ansC1k*}T3l?x& z%#!_2sGkO_k>am1L$5don5#=!O;i`%LsP<8p_U=dR7qyjK}bRVAq97b%foboHTrZV zXwbd|qa^)jT->gWqo2yvHRj}+WlTdAGOSu5)eg!~4h*A6`dJ89r|!KxAec-|Ew$ZJ zLA!NwyUo zJ`Kp(V;a58p?O|mj{8TjCt{$vEe=JK^y*7ZKl&Lqp3w|3%W9TkH!(3PU!L2(wq%sN zEou7FMEDGr**u$=&r0jdrgxX9MvP5CNuT2 zI1eZ2C6GVw$9THz$Ke+-cmbLN>^%X0Fs;%(k8)lAETX?fS6w43#M42t1_o0PM;fRc zIpmr&#g5FjNFRo)XP*_H{xQhl=@EtcbhFDZ0!fdBUQ1ARO|DoBNt!&q-gkjKfGc{t zBJobn6Rr>GbzKd9%ARUKbv9*rEC1R2Oic;MRHqh5XC6`B} z!V(60nF550{gRQx@ghg2tvc$rPECpsR+u=td1WpoH@8*(%Vb)UDkuq;iga43F;#pR z1;W6)#Ow*3c#tDcKeVmq+l+}B|1!VzEW@W;R1WcX9C zyF`@MRe*w}2h1P!|6dFCuU7wGB1F#K?yn0(=33u8NA*56GnvBFr5*XrTagj+fBb1AQ<&7<+@Ed*=nW;1Sfau5h00poEi^}9i zh~qy~;!jrYfbRlLL&?0Jz<_$3K3i4_Nygh<8|lW4zRH)E8p`zgMa$yz>A(*chk)i3iw(@ zZ=G$m`i943FU^iPb`Vp=g}pa_4!O1DHM_yR9xsdUVMU>B8oCf95p=%P0Fn{|+c+ve zys6Mh3(xbQ>SEp2JWb%RwqrLAPk#o@72tB7aUWD46TgbyJ*{o1@cMiqAlV|C67A-J zA#7*S0Bd>Kp6xd1j5YNmSFi=Th)2{glSeMxLZXK*khf+v76arhaHsO#o3Ts`TqJR4 z{jTl(@vOjy4tBhisYlAK2LDKN6hYz@e|2%QzVhKPrcD8BroE-46J(N1BnIGuG9DB| zAjQGPj1qg`RY`ZLTpmQU}R9a0hWeCw;Xor;FS*x_}->fU$}9;iYWiF&*uNMvxeZ z%}#Uu8pL}hcI7vx=T(-fyOb23`C4ZEixuSomaT5k;de4C3{{z5MvL2%D6xSC&ghSn z2RB%h;m5&Qrpa2S2%>%tdzMnwN%U%(Sz&F=Zn(oz_X#*CGInjEQuxN2)WN*tG z#o-jcq?~JQ5!mKW_bq&ziS}(dUi~eTF!nNnDOltfJ)jUE>3hYjTq1tatrZTV_9zSY zP&c+omERIm1VoR)=ztJxi{QR1Mp#p5_z@Z z%$~6fw#7^Q__l1lBt1Oss*m8{SkhpKsI{C}LiQ($H5wk;exso1F4 zwkx*Hify~X8QZSdsn|)ywry8zR{mLg?Q_pM=iY;VKg`$p@Qu;G-g+CYx5k{F55ZtO zA^H9{t;wI8B47boY8{A#)qr$`%)fe5{3H4N|H1wN$%?Qgl5bOW^# zKZ(uz#Eclpygq~~a`EFNV@EZ?bwLCn(X|a8wxG*CqOE3KRH~)jP3=HtnWdHshCHGl`=!a_Xp6vMK zK`CNg_D`C!RD{frH<;%bdJSCl3H!l-+fD^W)_-ysFNq-g$3>S#@W@4>f?@tv%-Z*J z(k1B^P+qe`F!=E(-5}A1ThE9=Y=gKU{f@Awn0kXSd8${uy&)EaQ$Am=_{EC_N$*G! z2`;$BpGX@(a)_Q`G4?YTHXmE}ZuOjVSwx~u&@;^8TiC_xbqvfveJ3x3c4s~bm3w|71H6*~2@G5jlZ`x_ zYnb_5X94_Y#D6C+{uB|wozCP>5m^9>=zowU|1UZ8KdPX=^XRrf9v#`QiN5m>j}GBe zQ7pZ+tqlpRB!9{Le^k;|iW`0C5^tS5^b6kGuK5RK?$sY6y&$T7N%?!ivg*(E@k##M z@w2n>i>a?$ejrraQs^j!1=y;&2z9tlDC1%%y>G}a`|Rj!eA8EP#JVhWeR`%a#j|RF zX@DLULV;-?Dr3iCbsVqH64{u?O0{GCXi?sxt?}>%D?T7R!r}4EYruKkwYG^A|FNjV zZzxAAB@U0m9bc%%X(a{GcdNkTZxJMxd!*3K(SQLx1!X59@9Or^>L2r7^o9=Yb47pVam z25S!{ayGc9XT=Oc6BTd9*ut`X$_TMF<8dF>Eloh)uk4fr4e^NMJVL*#ZIHV{VzVrNnCw#hJKexQPfyJI`*vaT;`l}Kf(7q`1P5{#< zEEt0z(xHwPlSoImUql4IA_YfGv4PS@bYL|}{;TciPvQJ0g-W*4 zhIAhj_B)HY&aYzq7ZCj?aHyX4lmIyJJQ0Vw*B@&Va&l;rv**MMfwMAEXHCxdsw z2*Ex_WJf-fBSS}Pydb(ghA@sWzECsxMQM4YPz7_cnSrT|s4^)pYUotTe);5P=ANLfXHsLQKhjPXwOBm8NUL(erF%|3;9t{Fjjm>^iaZM8 zCT_K3WxV`+oYDCNnsoqy3|K2}o}Kl62zI;`x3HA171Yp$=-nmaDURRhbzgNvQ3;g4 zgV-p2_s}|1OdRa|Pv3E7{Vh&Sfg{u*(4kW8Up)&^Q)3%oJdK_CKL)1%$M+ny73Dul zZ31BmioWviyOXyHO&{x%s zUe_^a-cN>JV?hQTX#reO*e)ym%*BR$5opZ8jw~@MX<<8T zll=~uM@C$+l>m5L3U*zSu>D~A4kRw^6)d$COHL+9ZptzCO+X}txNW@~r9jw89$TeR#GhhN11-eG%UU8+@qKhKQr$#l@C3+ zy?QSJw{wCU2Z|H^{cR*;nY4;!xQLQ^Qq*}<*DT01JI|3D+pK8I0j+2q>PLI5?opXZ zLEIXq=m1A&o6Np`LuI?k5@sW&=ZGF2dgBCSy?PXBRjR}(XwbDrg*5-PLpmtJiix+H zK=pEHPBe3tEhkv=R-lTzjF0$m`AQ?Cl`2h10Uke3%r17{GLiFhR|A@c=?M#d^(>Ao zx@Uf6@Zmvl4Vu|*I4X})LVoNhx(aTwt8R}PA}dezP8&drfYU)mL|+2b8TSz9@Z+}V z3#D}@&tarY$3C-cW3uRKAdZN}+?$K{nyAe0qfBWWOg?J=^300~=-_IXdR;SY$-~S> zMWFepkyl$U&H7p;d*6eJRb*sU$%yEZ;4@}cod;D*$JKFidJ(iS!Qo9PCNs+-lS^jg zb_*b}KH;RyjcpK{KbQxYBjH8d2Q%VSqQy9YaC(xJ&)OL=jfWdNEnzE)BUq5D-3i+5 zh9I1CoV_eYb@G zEL?(j;b+0UD)*6wd)}ijO)BwPlk4+onk&ypQkCQj0fJ)a@ugv7FzMZl<%8uWMP+|K zD{WY{!J1CLK6C>v?R)hx0*3);c8kK%m+!*s3LBEF>$IuK4@G=OCGpC2RYf*r#|Wq{ zBta+#Zn`@X%wOJ-l_E()3D~5SV>hYJb&*m@W^(^yhmnJcWt|3g7Zo5@Q}|cw`tJkJ ze}-!RwtHbm4Jlm&^Q49-{nJ3_4^-=x6sAi6exYs7%T5|NXGq_Gd`IkhI{Uc|D~JjV z9~t#9*MU<-%{(33&hAcGK@5#bnmz}qg+qw$xOKDa9fz*`Sep1uEd=aJO51V`+NIR{<{O!Li|9boJ~?uM9x(*Q--gvRN4S4&RkE>>{EYg|~4m8KK1% zaAQD2(MV6R&ioxI1o9*xt-lstP|I)s{;(NP+KTYYccwzqe1K`aQUCPED+2sG84JR- zwj9@_tYh96BEhX!y#&v(n)fM-#$nC zCRM+G2D7U92Rft6hCwhZH%s6TrRcP1&Pr@*Gz25gG|e>PM8i3y_BtX0r7u>CMG zQ-bL~qsJ-J(JO(WsMD;{r0IWFvVaMJplZ_Q7-6QXKjZNRebsn@ezPf2FJpZNeOJmB zFi)jw(oIO`_rB?x^8R@STxxj6^2eD+J08Hgd146{SeM#mIAj~AoB?=e5AV`?`eb)= zKB;eF?$<7dvF6u&ESNFdCrSQ*zt# z4Dz?VT#XrBD!Xg4zIen~;^`lA_1;Efpq#Zex-L~}8N~?SVj5~es+<%TrBHHUl)~G| zQ5`dA#!vxXF?NzCBIKm)d#W-Xichp|KA4q~yQuxae5I^(Yvi`D3QaN^Gcxj;tM;#C zWuOxvE_GsX!f^M#4Tri7!h0VESOy1};;Ai_0dgp_x~YadNBJuvN&*geR2PUyZRg7H zC{HpNJHRWX^_Jw9lD#R)4Y$3DuqEP=2Cau2{hZpydzrsbtfTo^)SHl&h};??d!ce& z`4N8eTpd1`xV0V6g|TU@qb%>yO*u82WOWC~*6joI)%NKvA7)3aWM%j&a-GYYe(A{b zn^|ctT=OrwAnew~_#qr)_oe|{3CNSQE{ZF%Gvf2{a5-FUcAyE?l&3@p1a~P3Vy`rZ zrj}<6PD{U+Z(FH^nCJey{ch-yn{9(ufrdZDlqQ?!>2UuF5Ty_oDm}_H1(~1 z+|pA*yF^uY=bFNm=9t8D49;yVTI_pq!AXac-7lvLC;~@Kqm5;)_8cZ_#FRi6ul$w4 zTstpw2d^TXGBb2%@wb{fd*%$Amq1URzJ+Fy`JGFE&;-9pp7^9q9HPYPtVRl4NRhIg zVPlIXk8=W^$LR(0CplUwBKy2joWa>KtS~tTC0C9tnzqn0|Hm7>8lN)nr>vS5kY9mF zQ~EY%AI2L=u}f5>Sarp+ zSInJWvg83`TT?8#e%|Y+J3a}GaUA96OUQS;!y``=BGJpCmVU*;AuTVMbz@Df8%!L( zn6THhepCdyJr213AMy2pd=cxq)=ByUNE(G9_f)gc(_uuSa2jQl3It0`lWRz*9YqC6 z=LAPW18B0yRnx1aCJxy-_r~BVX~hyyDBy+t9eDPhtox0-w^UP{%y=iQVq4;#zsRew zu7A+`y~F>PDebx>d|Z}YJAmX(38<&lu1z#c-sAX z#rOo>e>pBoA%zC_GM(=HuyVB4Y=0^B@pN<)31Zi)p`;r+0<)SomKJg2D=V%(*nmI^ zhm$C5Qr(RZ^TR(R%VU}4Mk%Y?SgB#VP?3uqoe-;nGT#GLVZ=jwJ16Bj3R!>_-Mqj9 zsbt_Z3Z&SLAO|ZJtKV3CFp@<-0huR7&6yjv0#H1UW<^thi&h3oyh=fwut9WCLbjhWwBWqP^;&KDJDaWt=%Il2pUn=r}hEgR@io zEz8e&gKQCCxLRHn{VdL@I-*{LrL@plnh6NLC+Cc~QT_Q79g7X<@q`;c?d2JpZn;p$ zLsrROSE;PdT1s{vuUOzrtbp|v3nK!~;H;j?)fFmK(PVk;Br+qVpk(HBqm@~aoLsx9 ztW?vF0gf52@VlAElqTGf-R(Ci9sw zF|PH-f~RL_%AvL{Qp8KGcL{&>S?kN|7=WS1zLP|~5zSF$Rw_YuvyQDV1v#g-0j({a z@+NlN7cg4kBSpALMkit-}uy}H`Z9} zKLHI*D^tq~ZDlwqf{iU%?M)km^bqE}zU|wgl=E#A)pFo4&ye+BOyM;NR1r)&^O)Pm zmvA%ZMlKKVuA$rXSHLaqc?DugbTSoeo7tF3MJl_OW17h+T_7wAKdHf0eU7eT(O0?R z*^Ij`4s5<^3Ut3>cy1u0W!Y4@5)e?lA{J0~`1C--70C_hzGH_&M9USbz}1+-Q?#uz zE~V+rtqfoI4yPJ5`Xt+P^d#Kl$(O&4++|Yq?|Ca)#$uu&WKWO0^G^=+`l z57g#gMym}iB`CAbsVO7n>LZlSGRd%QXG;~;PY15~n_esS7j;|n{ygF-obKuX5@B10 zL*wpoIB4C&TOIenkaIQnBVvJ%Hg)p$Cp znXp+IwCpY_=i6qnjfk3Vhe*5oBXjn>!QI)9{d@1lZFXOvq>--m`rO&NV5oDRK-sq( zvA$5|-ur-8!5Gn#v|whoT01tPNPs!0Gk7If2b@Xs_;~B-+6s-QyB9<+d^6kK%09WuK7H|=;>&_{g1z5dq&1H49~;L#tSuAaZh-j0TEHyf zq7$Q*3#|gl`s0q<9Sdt`lFgp{AVJ4BXbr>N^KQ4CV2fW2>r(O%BgqAP8#^@?i{|5W zF9d_Z*IaH!{y6CCtufjc6G)pbjc(7{?PUjtzH#61xAor%@0ZsyEP78N*{1Rsc9`9x!ba-&=4#{UaYu%jq)zSfzwJTy*L%f;5qBky+}t!_jIPb%MNt~{ zi0L|G=52>O%X~|InmO)o4&q;!0-`%2H(mi-@aN1H62qarcRm!tZzA}Hu1u}|?1c*%ko||qaJt)F_eR2e0%&X94;&Isgp=)6ygPi^ z<2O&T{zN}=9M zp~_Cg*~4C2QQ76Nydu9Y;|@K#hHOeBxWC7!jZ{Nc@(&L+_5q&`!;UX4E&ZW3_NRoO zp(kr7m4hN#UnP76!wz-6N_^-FG%*Ezn$}Kwc~ru@NZsY1$W!x0>w6KTM4 z=(xD1h$Mj$+SZt-|89HYfz85ZvDp7{4v>=pPf0;LRgKZIJ;Tk^vDh z-8)qpeM%fC5NU=+#rzDf*^F*pKDrG5dCwwR$a$9I!cqa`?*=n}0KH5oFT5r&#nABo zN-_Mub^!k&wNy5B02(_139Y{@RsL$y$?DdBn)F6uVY&+sJ^3BvEQgx(tkjw&%Di#~ z_LmvtIMoLKSW6b$Vt`b(taJ}_m;Y8b<2G$2IsLHmb)xk%ukP44;tSq%KIvIZb#?no z{ztxtjiYtvqstG!cY+5I)SrpZj*i{ z%01FN2mLc&Iz}Ofl-lgy)9&WDvg;y5kROdTr% zwl2|asZi4Ylh&rR@EfT?vL2R6R;cgXO{e z^phpKw)iTJkk&K_T$3!Nv@3%~J;OZVDvMUNPV3woW}DVes=5R08GLd|)Q;(?SqHN5 zWmx0Z)wMDJkXLqi*UZ=TUg<$jnyvwNzMz%SBUc~BJ!lL;Be~w=2OQqAOvnd`o-G9$ zzd=L{&Je9#Zk?#$-^E`R2)@bA3)>runc`;_+eM#kRdcPR)iveuiWl4P3>HV19dfiA z=egT?wwGEb<$@nie?|;-++8z8q>Q|&2A~3q@$}hIwmPRavB*W zz_Xl5!WC-sEnSr#KBWkaCg8=lo>6Z5m0;Xd_0?1m?zCuBOTri{v$pYSM#oThy-_Zh zve|d39A|?X(s*iJ=!uRYqbZVf@3_f0u9K9vrum><)%gIU1H!gssb|h*qTTwi!?CC% zz7Iyw0Psw|zZVLhiAdIQT&=&GVO!cH5cc^^$TSU|c8RrvJv*au?EXZdG9 zH<9+6z$%-q4|vaO+@;dUr|3O_HjoXd8faoaqL7(TzuzR-U$S`PBSfN%!5qvnK#VxT zA;5o*x1LHNAuD67qnrQOMwS3Q$9#u)`iOggUnTsUwEOLuc$GYh@EeR+ukG1Ep-8WB z=}8MmZNQ|f%ZBHT!dvw(`C9|9S6G+0&j4(a-N1P&A~9Zl^3Q^e07q;Ii#`X!E986q zzF!ceJBYI0->W^o=T8{CoEkKQKS8+x|KYL|H;y4SS*e$6_-iGUQJjtX7ph<_NR+Bt ztO2EQrWOT0GW}V*S$PY&p%2kV+fr>Gg7);{cHL0>0@B~uJFaIaV>zRB<^-MY;6H`3 zABR{!O>0nEz_1KC#-JTj#Q2$+r)hCXl2C+;Z=Y3&Rv2cu-D*y(>b&jo9-xph!P$zL z(64`hfZuBhaURVf3M6MJKwh>8`0f_n`c&mlkb6s#W@n#Vhr61+p*&2$dwta$@*~En zbY7S6MOKLO;0d$v7l1zbG1|yw%f18{+sm*tHWvsZ6A1skQ`;jjDEI&e|H0?80`rN5T_X37%P(qW0)l-%I%+Vpu7c ztJjkaye<7pZ@#Dg&PPy9S~cq9!E=@iMMZzk6a&1?!f6M}sm<~F=Bu7;md*0-5cYdJ z=X@O^gS^jAdMuk&-|;Wi6akOdSHG2kAK&N(LLIO^m6&E3c7M8c;QaJR#T5xLRqW{& zgOjD64ndN>QC~4w7VG zaJs@)Q4isrxoSp6g~I z-3+(Gjl*}bMQDAk-f^olw`mC$>9rqIYDds2QZJn%C^(}vN?w*Con8uZ6WN57iZ|xd zuf!CT=zbGEIz!70BZQ$gdfd4x&>w>3U=t^VmI`a*JE6PO^fplznBkh(1_B3YyAIQv zF&SqobWp3TSQ%89vncSpJ#_&h@uj9wxP7=4cqGmb;p{6(&zKTf$-ya*-xFYA``M-A z={tjE4mEUy17^0pM6t_58=U&hdF_{s`-+LzNenU4+2ja6LF_ykCBD=I%Q*L_A=FDW zi%a6=!NRU1TL>JZs5wsw*Bb;=C#w3cLO?6e_JMV6u|yAJ>liN2k3`}VmoY8j3ka{W zdXutX@Rv`C#|Kw{->z8(ASFq*G$G7;g_T9Kd`~X~;9Zj`H9~K;f92V|_n$)5Rx_QI zs^wc{JD?%6g9w{#x_6jwaOs`HM6F!OK8trWSdSvyJHt9GoAeNd!)I+ADFoZlTA#^k zb{eS6BA`Flf;9^(kme0IH2Gqlqih=W>||(ZKWslL&X8+Dh0ETHi4}2(;Fmc~GHAkQ zQ*e&%|FB^IlQj6oM5ddQ$G2Zv)P+5uLa2Q2yu6}}G0@M~tcw%_vCs;g)y|4MjDt1O zE_D(+IANI0R@hxC)xNT-#TmF-t&qVpz)vYoPoOmzEMU=(0Jp?#S9y(Xj9DTh#)zb( zHzq*^*eEdK%*o%?##gOHYggsVz7Bfc;+8$2r12Yt@EdH2)^e~7DOe55tKbSONFsr6 z{f)OM4Z@ulie>Z_OZzP=R6_L)EY##pdEm#DHBM~#6C$C?wN6}X$^;e<4PC#Q?5-CE zim2(E{J`ZkGzRA`1;)4jJ(}Oaf@4pS5kE=Fch%R6ckQ2e7jEahsP>3xeusZ6V@fw2 z1Bot;kr|ES!@j##eP zSjQS@%JMmsYT$;oIQ^(pb$#UF!wMTO|m3H_E%_r>e$bCQ}uaZ3C~& zj9NTKGs6ZC*&~gwx4=0qI>2R~@3s3wOJda5a5T)?)B5;%64N6UM zL(F&Jq0p{!#ZY|rSs@g|tSu@7atGCdRb$~Q2U&Ch7#7|7jubFEWy{cET7pnvD}EUj zca82)mTvxs0xtp+D0X4$^H;M`VyEBH01QZ*;9tQ8N+1!O8Bt&50yE`mmv_wOT4RnC z+s~$P$kMg)GjNg+n5!(QI_Y7P2)_&Q?V95Qb*iH9|OY8%_d zrtZ(@K)KJq)uc02_~l6#@4#zip-jI6I)4YbCfQk3!XUshW`jZumMz){1^0d5*CqW-G%#=9(Wfea@rI}pp z^sW)YdL(fSP1Esow~xL=rPq=c#~m-dD-*wl;1{)>R=2CmqI6)bzW`i=7x8D>*@HH+ zU?vL58foRwg3fM44)4%{&7mn6i6`2uX_`in3Wfj~l>+N#$~I{NaRI*=E;6$M)_`Q# zO6h^iG30CkKYuQz)|@UMb-TSKEdB0N2nHGQ`YDm(b>c*X4v^?ONhb)eB&LYtc5+F- zubUDkcs#x(HTiQyHntV_l+#?%#jKMNB?wFFC%HlnrHdNB>}@ll&Kq)we~b0k;#>DO z$2={gx=u&Tm4%_I0ry->OsWc9^n_qFBw(8pl&ixNNWYvQ=UZ&Z8rEwZE6O_16jM39 zL#PSY@AdBKtRt_HywQf^K;w-~hokK;2>0rS;$6|ra)`R*L?0pBf&Lu9pXh5i^wC_L z&G@1-hOio{nXU+ydONu~k4{ATC*xW6tco6+4_soZ9JF zD9bQqJ+4Xg#Z{1Y-3g|w9n-}$jp-He%i`W&oJ-An+YfauGpT-zyKXmmW5Dsk;x+NY zFDpNvE;p9=+S8YOax{h$0jzk1Do$K=T2(0e0?1<(u3?D4mH@K;6Q<2Vu?+GK#@qU!oG)wz_LU{>d zSv_V-XU$oCi}TXoSoKmV`J2jJwA6POTEx=c)YlK=5V^$$r}C1mZglvHdM?$|mS9Sk zc3lIHe-RvWPCxRKfWlGPp8uS9INJJm%Ks0s@K_WS!~v8M%>sY)|2^gZCojm85aoy!Dj_oqEQXLXmyn)j+09UJ)NbR-;eNk zZx3PNJ*9c;w8eAe8V-p&YE@C>ejKl8;=-sB${LB_Ry1_oKh_DR{z!l&)z*e9Dk$sj z@y*ZHR{U->K)~3@A+xzhj|k2nlhBSlDUo!N=-D>D_;gqMTpx2WH!4u z^wZz8Tz|#|=AR4mO~8YP13rNNBB1i$#sz2K@}jA&shx{6Kt|5z$Vyt-VOVKIEeOU0_|nPkVJgzNeL{vv{Li6q@h3EElSflm zJGX~kyk9})shsz@;e>0Q3ocujQ(%drIhv9?xoh$Ej=q0m-+9;J`4t%NH+FrD#Jve) zedNs^Wt7i?x)Rwm8cT8(1g9h+P}~2V&|@qEM7TBG>Z^KacYvbY*QABniU;#;22}MA zf8+RR*0%{@RO{$fmL0%z^~Z?V>2y+gD(}C)kAIG%g~fzk5qK0dK@fF%19v{+S=1xg?tS=yxLqrQV5gTB}h6dkboJZ-y#6D7hJ#do% zH1ld0t;Sg$!o#5yfqJB9O+~nuwDe4!l9)xXi8kbv9wZ@zE1=US->@lH2GmnAqX~>j zWz%L$6^MxOZaz=@nJBO%%44&D%}gv;~eqh3(9 z;*90gIs2@6+PNd`uL3~7L{ z+~qacE11A5=mJmk@eX6sxd-;RT8T021a+=t95Zh*AsTt@3(A6IvaS#TV!4)_zQOl( z-+aOo--%a89w3^6BRr-YraU`IoL-l#kJtIR7N&NubaEx0ul zttVc@pO3qro(iyJt|$?|EnZ>ad{kWho(TH6$9*EeXz}%GSFD3bC6g8jK&26r!4y)x32Z2n$8&DYPwn>dl@|p6F%` zXKZeGuHh{hch*aWi4uZ&2;j;GjYz5t*Q5`Yp9%)g^NBQ5bA1AWoZ^sVR7?Vqf5z2l%2A! z2{7MlO!&CMr_k^oWc@~?s?@2-SUP`==ak~?^C2FuWx2Wsy$IF1M$DoT>|M=vvkq^}fs zijd@l&6d>anM_2N zC#S_jq3*>A%Ur9xBrnNIbVH56KJ z!iWyH7g%0B3pf9{y2di{JltTKcC=YJnnn}Z@JWj&8*2HHU{{>%bfR%zID?4jHPxBE zIQ>e2c$~DnAZ<_uKYy(N8+xN>?KmBeRFuQG{Bu;u1415reE^Yl!OgfQ53|+4qiHWg z4d+NVi#-Bb(Ugqe4IBGCS^p$om^d3xXj@it^a>73UzX|w95*JceY0UmXcL{JbOd{- z3VU$9_nZ@?VS=+9o7I_0DL1BJUB9a7#_B>c`YD_^YC_D08Q08)UADpuA#r9xpEewX zgx0Z_a)D=$rP!otL^8xuNsE3h)E;haG zE?LnGAqSL(o+#HTG4eJGS51>LduG5T1JXWT*A8eMmjgw4g7f3)WSN6gkLDVS(W*U! z>84*>@d;z^NukX{%n2qE6O{;MWw%4Ck`zO*6g_qR2 zXr>n+%74HOK(*IOXrQT~Br2w~eO5_K%ZU97uC*v%19oavN28wREi9%uHEd zkFYfh(etILbP!`XQ9CY|m77y*&8V)xdD}Mfku~hGz}870tIUa`mM{D{2g4>N#kxQj zz^jvY##Lm#e>M{$GlbEitl>P*C#QBg`lH-sJuK4&LvbOad`7J*<3|Ng%FG$};jcD@7UCM%nEA3h_pt;UeP_)ex~gF47L4m~_gg1Z)io6$|{YZSuu) zH+urtPp3Avc9xcQU$+{{Z1s36HVA3ENUIiFe@7ehp(s3Ha3&T0BA5Ch(Aq}U=q$M@ z9Pd6UHHvrZ$=9%!?+(VlH4}fWR+b18t5vW*5Sq|6!M2;9AWQr3bUIUQh|}k@4zLwk z5%VxI^T{@J8yj(cX&#M>j4MD*A0+Hc_+>xuN5Sg>tE%Y(hQi%3Q*6sS@tw=6PViSv zCQD|NXq0LDyx^)zey`vmhWEpAX{n~4&AzQ)@yC;s?W?8;Mnp4T5DuTL%Dma_5VJ^M zsBA%oEkKuBVwJd2)sGTgYF1=mX|b=%X2@hqGZKT;zm*iEq%zG@Iyicl;yISEe(U$h zP}Cbscd}Ipp{C#MmBlTNt*!N9Q>8{4%gYTPDR#44W|>Y$-`9aqi5@2R z3gTub=v0odeP|N9Qp<#B_B3(0rP+wW>d2D;+M-D2uBI&J zIGwAGUO`wUFiv5$;+5`$0TpSv+mDI1nN& z==`B2ef6482i1O^+8@iQQ?eA{u8%-5bQ^XaKM+p!Q@1ya=j>@{9HVUyu`D(e3jyxW z#$X-c=%WG<*FsE(4#L|SCU9L-TwQYd`aUQP%f4}9TSH2nfE%)8_gY&5QA2b8!Hi{= zuzdMb!7_HHu#s%Bfp;oyvL?E7gmq8_xtS!tr&X(L-!$#D(7ICrS@`++hk}lo6K6u9 z>M74y5VvLjBhuKd&W3FP+1_V}M^ND|kVwlQ(~h9(Zs=MhqWpG7VR!)}+8Qocsv>_Z zx#bXKAq8R5HWZ309vXb1Pmh`stQ|y3xMbq`{1X_I8iAzpG2@>nDm<61PH69*x8q*~{aas!~2)ncAD_73X4GJ6*_`LT1>}1ZYUA?v4ynHd57J zK$T}xIV^)cAz*znwq&@Rsh50XI#L~fJB<${@4xYZ|AKG2sc0Fa6W)*s;^z8A;wn&P z6?0j1-QRp~xwW_krNluFH6oQs%#B6q8bH90brY)b+XSQ>UmDH!L1oFaYTmbZ0oCoj zln;f&e-zDlt2nMj-eBMV`t2@4?pQWl+_#g zsRL=swpmsn<82m^$(vs?$CLP~vJO*bk8?Z$)9al2iv3#P34r5`DTpSN#eLhKfGK$# zo9FHnwk0EQ;8gE95cue^-yiJN<41&Y2zLUNbYu=MRx0qp> zpkbMS;rK_j(KEHA66b0iq*-5wvQ2-*cMF@U+V=8^`gyInv|nSL?Ly9MEs+}ot8x}s z2bk?x99}*-0(3tKXzhCL{9`NB8SZB>JjpjvDfgCt-8Lk9r1E@fS?FmdDy`~*%*f}o z4z22lfJJD@<=zJ!+!Yzm{{#&9W-=ohP~`(S@x?+b69O?X6bKUiu%;Z*x(rF>gd zf8rb7nx@!Mk#*L0nyO#8n?|Q;tU)`R(nyHdJ?l}~x~RSC`f6@$ z)48Q=1+NFCd34vlf2pRKblrM-sBfs9Qad{v$|&eN=YBc&^tvaP?H{8WpdCm5f%@AT z;x|vaOr;!m(Bk9J9VN&QB!K#-F&`Ld1+I<(cmI%H1qTwStH#Mww%qAkB`52=KVR~7e z{E3T`z>ONH^gP740k4LfEK#g5`Yn^5715_}xSpu-b!B8u(bOkUc%A~I9%l@-1l`bf za7Bd#Bk88#7FJc(Yxp<-Cw3MIb4|O;$9wQaGR~#t|7v zxfzy0@QPVe3<#WSq2+Xl)lrms`69B980&#q!Zi+rvY;zTVEK^c*5aziCVIJPnDQwN zlF?G6S|IE`6Ab>szS)-jO2>noj@Hs11UfrT*RtiCmHCP33t|BP{tx=sSKAZ@CTrf> zctbEadXu$L9OyNU;Vl!Hh4qE5zYk4+C>Dsfx?hk$%PcFP7@PfnJ<bwL+ zFyaa4K;YQn%264+wel1i8X&`EDz#zc*dy;)k0M5>FclptjnPQ35Z5277=6-4HTKXO zA}6qiv8dLChnz&``_`jN)9PmNLcWyjXTacnJIh=mLYp);izoqsD>C*q&c&XVPOBp2 zMDrrh)7DLDQcV3^#A41>m8s|2tv!Zm z%kf0)zFM+UW{hXR$Q=q3k*E{4yBUB?*C_3ab&uPDs->s;3umR4M7g(@a|)~Ra}$q7 zl6H&5ZAy^$=o*9lg|`}tIJm+}P8Tj&@clKuTxB9!$t4}<}y4PA53?PcdUxQ{Vs9HJb}A(~D0 z@YgmUm<_y2UFI!r*C9}}7UPTkZ}gSZlW!%Z6DfPwbj+U3efl{oi^Ms<8;YkAos0l3O;dussPfW7 z7OqR9BzL1yz7x9M%;g5n^YX;VDb0aqAr|lLAA2oz&>;jBkrw`7%hxS(%6Vn#!r*Qi zju41Iu{@>zkRMBcO%hwM&#fTOsL~bTJU%;d`9S8JZ4^Sdcx;+H_mm`%tMofi0I$Jz z?!o^kwBdaA0sdgif$%3Of$NLN1H;s0R)M^6Big)q82P}*1D(^^CVDNEbyRDGpU}kR zd&ZXCK!(A@#c=tawQf{pbop(vrS!JOqSBmBc2-?+?kos9?jz>!af=9sx4&(({~=@6 zFolP^fi&4HP#5+;*zx_1Dg)jte^I#qyjZeT|GBfS!KIA^2kEJT4pBtU2n|DnSz+`! z16?-QAV0U-)E2ASeP18WXJ%`M@rpIvfNADw*QW);}7TrlvIqHrY1N?xRfo6U{K;=35 zo$D5A&c##80D%VMiuL+@Du3S|xt*saz-!6@E~|HM#kIiZxY!Wm4y)BQCsm()XloV8 z9Y>qSZVRnA2cz0WJVmUBg>P#I;e_>K<)>ZOHa2;eDKB2+I_;|UNfZP#EfrJW6`dpK zw0ZnIm_oHOR?aL7KvP}QXN2*frQT>?mI1!;7SL+NCHz|>+VBTe>F5aB8vNK^jA@z# z*SZKq_3zo{Lz-;jJAx9l3mZZT*N=jXF}91hUObN#CSU2P?_`C*#3K@so8DF&VWYUb zcZimwdBnbYf*Y};=3AdRw5Egd7KM+orpx{3z&Np?7;jbz4eR4Cxd5|%w*HyoJjf~< zGd8|2+5?q<&|%3(Qa~PjZ^4{LlM`a>wP+vW9dp#z6cNDhZ#VtZZXHL`Gque$_?)@L zv8#;b(fL8~k~v%59#zQ~+A|KRftfN5oyCVl)dIfcIw5LJoKJ5L#g^^);}ZmcbWz*= zOlNtCD%khC8n&6l0%Y!f6ideu#n0gk$VjsyEy6)G!jvNnbseNQ6e~F|2r0OmB2?^k z(B$lGp;vZsKQa%I=4bE=hA~B{texZP5&2Ez)E4(>szC!N9?b(N3WNhFYN?LN^>}Zj z=rB~5M#Qr;m^(`(L-t)x(UQRDnN31JDNbF1b{3W*Y|Yeg32)ZiV8r{1e)WBOPxKRc zwOB_)b%PF(G01MP#Nuz^Pa_FKb@&QW7_RM>NK%0P-JS`AU5|bjGnz@uZVnsnhWt0k zzthKmdhPttUSuF}@rfR|#qhsp)5Sa-OpRT{Jd90&S#$Pwe|6k!H643AG4$Vq99rJ( z(Z)yWR1Fu9r(E*uvv_QS(}2Qh9{C1M1yT5*Wf7j{V=jZ+N zzSde3V~#n72@a=?73iJ5>4B%|#6G8^$-`vlI{<78GxO8l_a52AP7sDQ0U+6H4TE?5 zgzoA@l~cZrghQ?+o11$ei}Iw7=o;5tv}X*HbvmRc+-rgO=q$AZ1*2;L_!(`tr}NXU zf~6gHOKf&t@R7f zd`auP)7a0u{H`@|FE{Tm%Yc)X$#H8<8f7z9l}7X?0z1wRycu*zt|oEJ(KX`eN)2Fn zV1@OuHQD30<p;OaT1=jH{mqpgVGW$XAMm0j4Oz}HmyX6 z*?}r>T%+a++}sRcLAK2mjnvA3>xy3+uEQ_K&a8Ks4)Y3QMKVg}^3RNRux=oxSB<`| zb_-o7dtj&^x;NkA2pwjxrua}SF)hFsuEq2oh$=jCj4#Dxx84kk7F*bt9W}@9t80C$ zt{QwfSj0+WS7H6v9A|0c9QwNq17VG(CM9Eb-O|}rVgl~sE&6Y@s&wPJi(MT?&aiyb zZ09__p}jYBf|39gH8wX2xF>7K{fn1}h(A>|-KLI_;M>)BAWA659je@I!UJx4o1(Po zO&V8cw#%xc@h&P_@Q;qPloT1tQkNCZ4T*V_2Qz#^2q&vyQ6(p4 zGkwwyXTp2`mvm>eVlzJeIT^D4eBNyT>oQv0#>Lk1(+$PR%+}~1?>Zq?TNarfIcSrC z5Ttjj7RuYF3-Z$vP=;v(9YzOTOiHEwmtKVG`o2-qsFfr~2AEGGgqcAuZx^01SBCJ< zN)o{IZ0783W)b&!RF-Zh2q2)1o`5gM1&#vB)I|UlR0lc)Hu*}#+ zU^2sr>F4>)T>@^ZHb=`WGK{gxRVCll)fM1RwxEX$Muuo{U8gzZY7Wf4`h49<@cR`6 zA{9|`kCPud<2Oei9rB(G&`~u6z}p8Ya`8u5Qd%d9`;X zHo=b${I>|GOBsIp$@H#%OX8mx5WufVfB@{4v0aIhmY2Wnul-dKf#l{zm3>~1v`;xb z{{QtA`R}vT|1N*|DU6RL`k4co8d!taY(VVeej%AzvJgY%n2!*{|BM!(4zUmfg9}|4 z9imd76!8$hO~#8UD&@^dFjEeg6B{=ycrPY1U9e2cm*;cQJ#e3R%(xs&N%7fyyJ!6+ z9?v+m4YcY=7_XNYj|hWG9@ay3guE!EuM+6?^YbvLDwV5=9*Ffb{~U#GJ-sH1&PnIlCRz91vRSWDbJ}S0M%NrAUbRp{Yr2A%?;dB`KKtG;WLcD);^sK61rSuF7H`$0~ZW*Z7b80<>zbYTnWhu1>XG;q4H6{(+AB2uVJP z9l7sjgw^N4GQ|g=Lwc0og=gV=%y*)RdkY#TKCGmjV{kC09--^Fi4yS_W9rm7w@9?3 zR<-9Td*`E6K)E9l686U3mk$QciQf{0v`7dH%j-Q7h6w9BP{QPwX-KFfyq=% znEgg6f!y=h_vV=Ljgu)EQw!bUL=QL19=wT7Rp);V7y9-1m?~7R&Z;O>CtccKI z3fJ)s(Vw84*Ul~HDD>`g#|{ZD`4*~UsdOL4OT6S?gLOo7H4bTLpxD&s--(k~O?Bq2 z>kYDP`d-52IgugBnxAE6lhu<$=9@!PedkE4A_y}-DNv4`Aem+lQzEl_0hOwSiR)vt zL6pMeAR$1{uu}VROS;qP1z+gqHr-3K$#kU?r*Ts+ z`GqUeA-)E`lr7Tzo6o;Qf--Z{5uVRTu=rUA{u{Zxzx77{JrW4&TUqHFSQ#t+C+*@N z`J+NfU2#?p`CZ0L94jwg8lE*#MG`7M#LAB>QMrt#i!RSLDcY9~-+l)44eT97S6`%S z8pG@H({~sPGQQuKgJ&<(Lw4eM!Zp#u=k4tVrt7DUd2`Trah)J0rmdVXeHi)(vGL-y zNOSZ-LKviErE9hRcqN_~9a&|mGC*Oj*wl#cm4EDyF&Kfudh*6V1n@?~S}eMyKh-Q0?QL}Lqt--S7IgONM)Yi}F^E;S zbDCAnL^NGY8!h<( zB4YtVc{bo26^WHW7{t?OYewC=?M@4PRh<-|aAsXNvxr$MB6^r5mi8NWUq;@RDKmcbtar4AJS z_lkv%qB?k<3i&0J1IDMIP>6sS&GKpG4}mPpMmzeJO(~lK%D5y{M~OtoVJNTJ)VjQ3 zb|e?Rmm{-&XPj}fm^SE~L%hlmri|I?5E7i*p<=bEnvg>Zog*^w=mSZ2ZxIgxg+WOp zr?qbFo--c|DS;Eu`yWjoYCJs|ib=v4D{!0FeaiNDl_gc4qP5`;0EaUgiK z#Bj^bBG&{5F5~;^KoN#_5lZ5sUR$pNkH&xv)7Axj&ZY z!g5yol<#WvW4dV&HPOy@9`QcS7TIFnUn2s~5q)1{@nWdao zXCpy-?*$65F^Bn}_NOJh%<{bJXTNY1!}nhb;h5%BpX~t7rz<&koXUDif;-VIncioH zl$Ii|#5IV4OUH+donw#i@%4Crp%uRRa?7S}M;q zsV~CvhPHFAw8|=XN})rq_&{4Adj;SGn)jSqW&^<&%A$;PHk<(Pzg0;>wa|>#^q%7J zU^ukQ1GR&m3Y%;h^DTQCHbpA9S3kNQv1_H~{u04$q1|&`L{F-q`_;V`Q%_MOoj4+P zzn@sl-oD)cY?dvjd|tulJxJ_bFWg_v!f8X$usyr#TCIY8Kz2cAvW?Mqw~L6#ZW(_h z0W`UaA_iA`tEH8)u1`90u+{%wch^XCOn@=PfWgl9i%2>yvjDwzl&|dPnu30)Y(T#s4!v?sBOuehvKVwTYQok}aiIjB*U-XVtWQWE$l0~oy zjla`e6#aP3H;j!N8!-#Q9YsC^9fqr@_6G7_xWiwHY!aGJm|~x~Mi=hi($@abi~Ni3 zU8KsXF7`f(H#n+^n#d0Vu1APMlOo8wrWc--XVz~qQuD`^SFgQ3RY1-AS z&;R7MlUg^RSzAt@|foY7Db9rXTAy3KUVbo7|y{CK`vu>SIT zeaQx^FEVI_ZJb=vGe}SBpSLTJep}>h%)}SRO zh4tp+TX_&QAzHa`87UfDN+tp+)}2K$rmKlI<>cmp} zpb$lMT{qvrh{Ev(!erKYOSEG~_Mkn;s%wQ*RFg0pe-ZPn!+Z_HRJrk;w1(gp_(u(FX*2)G>A@^Ud|UtXh9|T5faW^m-Hk z4@{d6{n}Em4NLV}JWj2gyj$8*_T=GXRVzekWTzQu` z%9YE(F@o{Xt?Uw`3Jp@a%EIh<<3psl)h6F>$CFYF^;Nj|56m6^B!vfoEdb4Q+Rag( zPA4xXpT^dqLPQLBBo9mZv^sp=K!qFquvT|MP$To1KF_2C9VPk>-+mQ<3-<}gDXC1q zDlu-p=kbJ*CICOtGZ%t21L zze7u;2nl9obCva@_;PJedsuXaFM3kgk&oFX4B;lT1l5sYke!@oa=>GEa4t@IN*691 zS^6`)SU5!oX^^gO4B3C~@^!(u1R5!7r#dw63Dj2kI`wXnE{tS={*ww#gx6%sWe&qE z9BZULcuL~!t9$a45URTy$u;+~^3!=va-==(L{g+l%*tc7&(3Xv`rsACApE=D*%nvnQW2*7fEM+Dx`N?igT34DYUA~hT+PP!P zVUq^X{aVr$Q$=&R<+Q<#g!kicQ~C{LvKo1Tj>;XD`knN>KdqUDt};oh+A}<~exLog z`H^Ncm%{yV?+z?Tr1!$Z4NOg_&IKN&J%i>hNgL~RlqBPG%Qp~**lF0@%+Z!JE$G7N`yra&s07rb=Bk9fuqaH0&Z2WVuQLFYjiFoX`)QIyoyQ*ePB`dkKcCV1|*jdX^$$6&jjkE3AT?7#6t9j@0Tq_HVeOCC>qu;(^uJLBPgP;c3O^#Lq z)VO@yAD6(mPb|{#3+a~zMEDM?8C=4UMKWS7FC0yI^$zHaI)rg$3vW0SEgF2DnHE0_ zn8hhE4J(ubrJWArx$^8rXw$w_2jg*Aj<2m2Tl`kry5>j^c5C5YW(9`lmQ<(VRI9>i zvZZ!h0~y2Y`OuV_c(h9#e4vqzIM%C(P}b#fE$CsGwZG)RTYqd+bdAZ8eVe@k6&a$2 zl%@D0-y8tiS@Cw#-_j&$(3Z8^tK|M`1lA^(NotpTM#S8@_$_p3#YDc@4?I-soC34R zs59bqleXbSo`QSK?!NM?8SqQy9vWdm%%&Rt&)Z@-U#l1*>YU>-XQoKx*x9v~2~_{{%Q*fMsaCGsw|v1ZYn)~K{M?j>^WUL+4~zB=ugd75iKoS4T!4HA+Y1VpnpRiR z2P*w4x+Vvi`r$$am2>RGDfwbnef+bIw4>G_kOGNTLgKw6gyUeAn2i!4yjIG06NlH$ z{$5+}fHhy~J=l$o7X*c)Om$mYP&qX#f4{+qM0Mu zl;~NDJNz*Dj>1|&d&a?8d>%|i>u?w)6Xurc_m4?ObxJ*9Vb0bd8^uC^}yaMc4=^2vD6|A z!I{YX6A4gBVs&lIWCwHMZa2e>3;Gp&ce8IU4x`IWGUz2?Y zr)eSQAM$~u0S*#my75`G!w=#(vI%*pvZsK7E&(pGKH5`&%Gz_7Zy^Q$lLlKPBZm%+C}RgXRTVk_#lIGFhTck#_+s9^5xeYa1=`(SX0XXA zeet>#{3cm*)|_)Sw$_22vna>KgQBK)f<3I7T>qYZ*vXYD#nH;a{ECxnmX5WNHcN(M zy0U!U5^v1tQB>HZJ!mV4=opJxDbVn>97Ki5xS%ahV)-Q~Bi?qH$O@*;V7Pug@$$UB zj5d*nBMZOj`ZN9?-!=&8nW{TfSxn*6tnpfDp7@!!EwYxT$HlNg9X>J zO?!}_&k$Zm@z@DgQcKrT3l0oMmA6{ea0n;Z4g2wMX`*agXRHNot|uEu4+ArF6hxR0 z$SsyA`MG(JpaD98CcdGJG@ zcM8*&`b9{E&bJlw$-L>Kv?ZPe1Q{zb7Ic$sbz$8eGN_wE0$rlV9i1r3Gt;`$5!2S# zWx4ssmM38EA&=Gx%iOkSwD}B(oJn{pn01z~(-*}6o zvQpkOsZYoTQq!TFS)s6Jbl29NgMCHU7&x2E(_xq9ODq+ph3^iu1vRmF*XLGspNo8B*9S1?)IJ^w+9yk;~lk6inMcX<4sbaX#p-Wq(`l z=*D7~(NyJ76YA`(oa<12D#o}j&y|k!Wjr;9KKdHU-PS9FfiX<<4T(Fs54ZRwnC^4j zWZ1hRmeSHkC7=UGdS-Y>DufK2Fx9kLH&Jx}F~t=9-O%LJC(=I&nq4{a2JQ~JUQjO3 z`)THP{OCxvC``-^txsAiA(K3LNGMoosY};`N2sd^TDfs=u2)EX97u8OTmn95ElkC> z8=B!rXZ;*y<;c>oFY5C~^=FYgzVORN_ZQhq-nGTPj3)@8dvudFBUU zsBTF(cA;}cR1@JhFX;`HyZ{otGg8ce2rktY`iedp$UHv{c{*(t1i)f>*Wy;NKF7wG{0egbMul|D06)4vSrJ0rG1bOy zZ*hXe$#<8ZeWIQ$E(Tvkf7B=)o{*`P7ND`t3j9!hf?px87*^U{UK67psdG$jDU-+Q zDW3&{;t%Y(fOvCluwW1Xw5pVH!y~!|%iEU}woAl?TMx||4Q=g?SdzO}#<@^9Aar<- zg=_ty6#+H5t*W(0cMOtuPBqow)o?2uR=-c912DOMX+6_O8=2S*(;u_kwRy+%Sx)71 z&R+R|w!ka{@h{F%J@p&E_4ik^W6Fkkdt5h>S6huzS5m{W_#Shn$zP@0Hx4SwyCXR6Ysa!=O8WzuUaj{h$)33kB z=EK1j#0A{_A`?B6Ticc00eMDn<9Qddj=MIpsX}FxyE&l*ht$&kL?*k4UF>fCd( z`tqK<<*q@moP&9P^5IE!-&LIB-(`CIAfLI>@5DYZ+a}&A3Wk!yiQ?qN%dZ!QUyu}S z_n=Z1;b4V;vs!_@`}>sLh4fy6qpDDGl!b~dRBm4xq_`CNbuv$1RXsn#b>$O+wKO*5 zlShTlD=|#!6c5RB-B;PO=dj~F-Sy4oP_Kvvm90F;Sd*!9hW=SZH*ZAdq#3brw10pw zd>(FgA=*$*likt~s$RDi{B`L9W7hH0KDa@G*TO&t7Jp@diG?tUsU$)R&FYg1>j$%Q z;yor;(jriBz!_uWl3)UvA@s~%ZY0`#5IKF*=?nMCK>jCH6$Nyqj-0VDmXe&?7XP63IYH}a}xHa2l4{@y5-A`NY_-Qf}E1-S>P zR7a5RbqTxko4xet9+nWicoO#s(Ueex?4M zG5HrP7%7-{X#0uYBYYw%RR43`rs!<$^xwz3%G&=Sa@CaL604stC($WqB~OoMEh@B6 zSU^Bc9hk3-IOWG9HM{Dx;#6qK!}kH{)1~Squ3inF<@I>kyyS|Q8ZV`t8ocH)xqg`5 zKFQwle42&k^QAc;3p%}@7=-YNE7BY%3C8THrz`1cqzgrB-d=jp83Y4)ems3;UCdCY zLKjPHw-9KH{FVp=!wCnU*lGk4{M(;4iiED?%(HKVqKa>|A1vcnZCfpGT-O8VS(XmGPF$>0_M$%( z?N2H{H^sF@Jod=(3a+Hc{OyJxA6i2o_7F$pEy!1H&bgoGZe1%VxKmW=-lfpik~Cq| zp(;=wA;8FZlvk@pucC3iWI)%KNf3W?5{KoKVh`?R$t<{FuN4DgbmJs}6#FGK^K}$q zq2W(+ePk+2_pUvu&EXy#5+oyP^EDes*q#227uCklI{^l{nJqqLG7 z^Ys_2t|Kcdy0o;la6dZ-K^buTG8M+__MV8H?=xqhnyX9tZc^pD2&DMpd3@VGOuc=E zZ)A4Yosm?We`vef;LJKanQ0dOEV<-!%v*1ItYB_mTRC{l37LppS~sSxw_DHGJh7QG zMP+5x>|TIMB@C}RHDcGIi5WYdy$F5lh(r+M^*LKkSj$@qO?MX2C_ucA-g76v z&8c~OEGRok$}V2a<^Dq{k6&QL5t#d*_Q+=WTQkXlLY;LB!-0+k`#q%(opc zPx$X6TUjxeHA?*!H(gZu|@V3KdtShVQfCVn+VA+vNX%K^1KEjm-bKO`f0{ zu8aNIYJsDI2l>Jage>)05rkG#;m;7U_!{VB=l`iSneBpYL;&$po z+YY&9@g~}9LL@mI)H{P6b5P7Q$mIF0$+8*5Rl+fe%UfZ$XMM&~;((`O2N!1lseEj$ zY=;LXp;;Ffm#o(xASMD=1`z7cqrj!?K4dEII_#g?ok^Fw21AiGQwubL$c%p{Psy6H zLyWt55{99?u1|Io=}%%RAFQLuL`tDe+M*t(esg{Ixo zoblA`cUAIK?In@2R0HM@rK?7$CUSYieKnn{MPW*xoUKpt*cItx zAl{*e%NKyGu1L~W9>iqa701o373Bw0ebAgR(=}m`>l{qj@yzm=qhwz(haE&JTxYdt zjhnedKiIf46=q3};3e5;-6lT(`+d$-UW_6B=@v3t2knfF!@xMGonZ?Cwx1b{K~QX1 zXpKc^Lc{*Z4nfI6poT6eGZXSzq_d`m;9Z|F8;_HYZIh`_uc2^a%i>vQ#h}w?ST)tN zYngdX;gC0TMWJN*f~jQS$;{N`j%y3`+3m!Foahax7$@>I8B3a2kA2^TU6ld0Y}{#H z7jGxWSUp-eusd0C_2yl1-CBoxtkLMdh#YH>8%|(*#^8f|T}YdX5EBA2k+W&3WTBaC z2R71XN8D5Uk9)gA&?jE$3PjrQm^I);?R?%Z?`DMH4LW9lmTJy zPM&_QIBSKUbGYag0kxMZ3`7PW?N$0EIZUo=fvWj#D2Gm-pdQX=NbPuA#)yvub>$Zp zU~<{6!!wn|`m2T1ubqW?aI89h8K~1QIY7(l_r3(dqkS0H81j5Q^-NF#V#gWL z>M;$gm^LP}sDZd?F**}m79@UA8A`K0KNAtEqdtF!SI|7A))CRTLtv(Ex6vyDiB(t`fZ=xDuX?qNrF3>t;*k_Oz|w$^bw z-#NK-l<6Vu1oCd-p!m%F1?cwcI=!KoI={jAO#0HXes{wet5W{eystBB}Eq57;c1|0(3UUE#DJ-?GL!RQ{V5wS4&*1Ei;)9(1L z;elPGr=64u_qT4}5xAwMu}E=T;3h6gN#RR4O_mJ?wC?ZUA*$5nN>pt3P^U3J&@}hX zbDqC#yoUTxDr_+^EwHSi=dl@>@ugyF)f*XQAx52MqrvS0OEZ z#58njZ1vt|xw$y(n>F#1rxVNN1+81+CZLrhukP>ccT(DpJ07qe=J4~ZD-6Vy z^k~vVQ-%BOm`3Z?3c5+{w@DWRnctR{#%S}tl;%%3C=nMdoU2yDf(`N}jd zULV^`pw)D8F@X@z0l>j>3K_|u=fNj_!b4{9PuG`uv=3z`TSt48=Oi=~cO?{y;8@ha zYHKaWHwJJ&!EXWkkos|)9WTGR66v6{5?)hwIjg8lrm;R_>-qe=BM4I<%j`k~LOw&~ zXUj}RO3r*MSXE8R1b`do%rj0EW(O%?zY4q{Cq9^Pvt*^ohPn^3V<`+Nd0;oMWz={bUO(9+ZjLtyIoV23C73y`g2tYm&O-Se0Fb;*UYpG3 zIZ2(H?a34u)(PYiteR!^<&wfujx?=__?^aokF0W^|GLjzr$#U5ybNtIc|Q{0rM7ob z6E7#OE16VKW!M(4Cq|oJeEVnl7eix{1F645FSL0;tbaUC;l+=FV6l)N%t6y3@{5Pj zk|x<%_Q-b1yGOsURF{HDq{3k6V!zmo@a5f2bA}$(+XZy`AuoTb$#^0CK&VOpsZ(R7 z^QSCTDT0@xtq|9z##3l^0Cy%YPZOwZqYo@|@2Sf1{^n~w*&~ifbyn#*3LOlCv=mc= zmTFrS#^Q$z=@BJ;%UQ0;Uo`ed#B38Lb1kS1%qb+|kROy&xAgR0nHc?Vve zr@5!?eSy^NK0t0f%W}We4pp|}X6>h~Q>P-eT1=M|&2me1i7N;cZ`>eu=bTk|ISmoX z2_Haphg3@h+Fhm92^9ZweaCjh59H_D}Oaemi&tImfwCyzg8S zy(!>~T=;&F*I-OvFV{xh6`pL?LPUO>xgg#C7({1GeQky>GGqH#BW}8~dv^k=nz{8x zRFJt>!rQNvlD`L9wH_CrVT3H(mRx`9x6((coeOK%uoTJ6_ve033$us?j&xwVU$=+S zYfRV*79yo)&2z8G7tMz?53SvzCCs$OnXhtp0K-Z$$N4?-o+@#>rChRlW%AAf?&FI( zEk{$#{f+6Z>X=*bAN!^?l;*SO&Jm}_zsXSkHIc48E=&#kq*kE+duoOFC-iNuZ)NVT z{}=oHKlFqDNtIRnr~7vjb%k(sJY+5b!AyAV7PoISd>81fnL$m=6H|N*G;&{*2iTj~$T8Y_}PGN+?ItqYt`iBjG^Sv2>F_1%jEGy zE2lz+saZC&2{F}0wf#%7eXhB@TW9g9VoIs#C&rm1Ymtg$%dn1#JB*3XBqpnd_7!k_ z;kxk8YdH>9b_+B0a|5eP)D2B$2967@k#3Tiwt0|Uy8p$t_-o4}LCPO8`14*%e@fB( z8+_m&1D(Hj87ikLNUF&1BoI>QBH6!wn$71D1W495DuKy^(wc>!!k0a;5P?7kVWdm_ zzI!jDoznIC=3S@KZLD4K=iKt{>kD|pCJ%V^37)#i4o{D)_Q$bPAD<7TF7r3D zY=~WEwg9Z!wRmFUQ6cJ?=7QbEuQ*wl$$jzs5A{^8Mk&+wi(RJ8s^PByx$w#BK&sE# zxWwQhIHvw_GIKQ^297$&D_vzT>NvZ6+G0&M{)M|Q-mgK5j{Hz9j+dG0+q54GM&?aN=@wsx+fst~e|5kX7E zNyHm3@w+R>Ju~I!vS@^6Qf?TOtcK4(uHZ4X>Mc57?KXS`GZJpfQ?Wj0qfPrHFWPEG z-;8~FM$AeWzc1&5BrEb5Z0*O9$oz^3Pt-aV?Cw;KY z_%xn2{jwaXpZj|txDU$J92YzfVXD0C%myP_rO1O~Ji2NMh>XCbI&`NNiP42skH+8j z{_U$<(Cyqk`r?wqWP&C7K#_x^tXB$27O4ajh6wViEz`3yAi$iA($Cwz>z3YQxrs5c zHG^eAZ;NV~QDy_b93iQ}Hc&@n1m5}tI+e|r2kCORI+T#jVh6-2nJs9S0~;y)Xx7f) zF%j}q8k&19hOq{8v2W0zK{XMf#@-={IIownCPI|M6Uhyf1Cj)HH#D>eFc9mlsgja5 zo~KzC&P9I>)3MLx5u8hRjnlC>^XuOvCX9r( zb$|7=Uf-$q>{*bjUV1-k7kq>MVh|!G>VOtgs1)xdzCu%XbbM zTIk|Ooa^F8QtIMII)LZFE##P}v=`ZDTkP&@0h6`?Q?SWDIT+7cM~2}ZE zajpD;fT<1DvH*}i`AJ!loC>Ye>1;DfSTYUJ>0vv*;Mf2*5W3^~%nQtBv@aF;!4QTZ zdLYLbBpL#|hysyAeR~qWG?2DKFUfVG5mlxG zp;-G0egkoNOl2HQ=9T&D`?FKZDVRf0i9Ld<9Rw@e7U}2It!=_~4t8Vsi*ue-x4cEV zp`3k?b7m@Lh0?W8NE^Px1y{YI#s$VJR{P!K0V*e*+g2mH9*X`Cl;eZnmB~BuC6sTbEB0qXhfv>$g%r-d_ z-^#zm3PCSDjO;5zm(GM_uSsl_JnoV@kB>hH(5UzpDgS}4s4g|rLMIMQ0L?S;MO3+! zW%g%!c&Xi>QiB#g;&U4+tp91(--?yL2K6n-*#708KrhJWe$T&Ik^lb>>i<*zE+K*+ zvga@PJ2Pets8+|*o{FHIfp#*okZiy&H9=_z2%?H$$L+6N@yQDXl%~lT?^hol?Rc_Z z$PvK@;E{IXDzr#gB_w+Qt}LZmRH_;PpYn(p8=a80pU{gWvkR1Qm&R^&szG0UHIr_0 zArUI2$TJX9+er4GY0Xm+`NSF<%-&~^M3}QI^&PyMkr^l<>N=~FLCv5xErLbl5ID9) zj)KD*r1Y-qY7NvOe~dfj*)+^_%Cn zwROkCx<99nQCVMJj@;CGtnrxFuDA4i2esE6?Kg!-`#}W8u}go?yIx0s5xCN@Oo2g+ zIbrm04vIPEiV*0HG6ap zATjJ!@p&Ro#EXz{zA%W}Ixi_fEgWLozOv#AL>3{IPd{lMVknVj+JbHzIIsK4$ug*; zVN~N;a7L~oleAR#zV<5z7{{dQu7Lqkj{x5M>nGp31&)(87i4u{wCy4GAt}>4b!heb zrXYMz3lhA_$4r(}Z1vCu)#^iRSxHLv-dPj@pLuL z-eq-H9Z)iYrtTr&5%rpLy()XcmCBRc3zN#4=n9*X%3tAex{u-vPrcXpp%z-!&D5Iz z@P|`CzpdTTEkv+xZRghgK6&O=@*$*ba7-ChRRaB@eV&%>j*9thH88xK8RJ$ivcTf` zZ*;3K zj$>nC^SvsJe*^~W1;A65+Q?BV*M$pUBaA`8Y-lGtJUenL>L~q}JMP?F@b}|L4a9FT~^6&(38Y(qU;6tdR!i_x;iW zANM5Y>#1WAHPmDy|D=r2=a_pfNgrl!-{&UB^OrHe_JHQOy$b!%4dmkns`_hSSf_3C9z;`aC$|L8>9C*u~UE~eCUg=b}w2qEt! zU<;!#KF8qW7NMpCa>#c)CF_K?wmOH;IM;fGZ^?dz{Xq*?JYen?3ppz2p6LK=iH?Dg zLVY^<JT5foa4^A1A8FeU%STDKnST$OG%NiZf^A zUe%uH_~{d<+hsZ#^N@WAxGL-H)5Tv)u}ZT>410EPJ(y^pbiB;)F#UMGjOzTN)lbV2 z*31$%+f36>ipev7{F)Mk#UWy+I7sbTN$9_-?&(Wg*X&=F-(0qx$)ptvKC0kJ-Os8} zZuX?o>k3V(O^dAQ_X<6qdT6W38AB;B8l{!=s&ZIu9^B(t@jXT}D$ zN5!P4@dr98QgaVX%jJrU6uep+=#iSFOI`bfKV3|XO_RoGqa8t3G|5N<&TFVOdt;k* zMU&U1>TtHeNN{84cyB?GG_Gn9(^TR%G=8Os3>hvbmxv5N`?P9~-^teTBtAg#4Eoc{ zBxgw<_(`9qj4#GCOGC~&e ztdD~+&=@eezfYwo3ue8M8!%FO^ZmP}6Ijk1o`p)I_GA*K*Le6Fi7sj_o<#d17wM?+ z7RVSDnf_YWYjQSva7CQ&XtGH%Psgkwt+?uJwM91Y6})2*4igt$t+zD@4~FR~&Nr)u zzW{Ga7@S6Lq)YJFd^?8!5-b#q%b=OVDNfP64f&DP6~JiJ%%i6lr}(tMUi@~+F1t3n z{VV0tozy^5IHABT{E=xR(1CdRff*rSj;SRN)BRb93l4f$6dTb|;b@V;%$l?KwkYSM zHJHh^as?TV4{@F_B-DSbm3tinFQ~*-sd1(YxqVak1u9=I`j1$dpOyHFh29)1UM!bj z8%(X(EUWE>f66?XSV|pj^t*IRrm_u2EPJe5gh7N#5uJD{6b@^8uNz+l5sV_GcziCi zvzk<0AX5HUlTshTTWI#W7swQ^bO6x));0gVZwTCLcHYa(gKc?KCqG+~!q*&9yEe*o z3U=Al4k0OolK7DHz*C?tVrQ-QNb(4}td2Iu7!ZU=4IQKksIt*n7QA>q^=V6IPgqvp zNg*n~4Vxs&7a!3NQk$8A_#ljbH7Jh8;g%^jU;*Z!9%`RWGHe>pC5#KV)=Gw+g#`}@ zYjvj!G}7S_Zpxv6Lx)nyKhiCPv-wl?lOsd|ts}$;y|E`sbg<*>NrJZLc{y82_t`fx@Z;AwN7f zilLw3TzTa+%~O3FB#>t$Cyj@XltOF-19!)awP!3tA~s*5THmY4-0L zRIi!|2(fdmE}m#=Y$^kirU5{0x}e}NPO9SFxa{^53pcWOSQ*?m^Wn-vU!+Vi4pW@b zx$oZzm^RvYoHo;apzmxR9jK)^JAOMqxNan#Rrd`AT3~H2$5(SS--_11k>^5TX-VRi zuGt>vfh{`cZsag*&A2ykl(DC6t_U<7g~C`5kzEp zS(}uv_Cw4<%mO=*AD|nAG$mHTkmrHlm9I>gWzzl33LC(7joMbv>f@Jbv_M=z5+q>QR7Zqr(7eGM$=Yhw4q8k4< z!(L;1=g$cLsr_tj^{ES`@Q=@brg5cHg;_r2_e7nUprHb=JpwfZI_sDag0P=tC_c`1 zK@4L~ZwZO@0pfA3;=g}~M)@*+14i)tg4+kMz$oOC?Pk@rksfkxy1K|t{Kryz@?cN| z@&(Z}oN{tG+kp{LbP^rqc4YuNgvPf=qA{pPFN}4-Ps6Wp#zZw_f<1%~QN&dAVrzh1 ztja+;4JUy1>ZA4SY_shu_+q2RW+Hj{!%RJwKjWAdwB=Fb@*VX$BaWtJKLSyQfx;Mt zAb&g8?R>1UD|#-N>7w2db!ubHF)Ic5<^m8FSv>WBPsVI4J-H3rOjJ?n;Q zWT73B8Vx&76=uHjoV(1;TF1MJsvV9jaz1^w9=sr=4MCnB1|>Aqhv0Cwb8Fp~OAD-Y zPN7YwX494XS?(>^k$J@{svrD-?%_T_pWI_T1VUS-gX zlqKdh3RcQH9*{XIQ+nbkMVz}Jh)MJFNfd{ZutjVcVMeMFwC*ZSJC|JavtKAXwde1C z1#K4&X@(wAoR@ugf;CL)FXpjdw2ewK%#sjsPyp!br*z8*90|wRISs6?(21jXx&Mc= zcW~~sUA90c_QbYr+qP{_Y)@?4wr$(C?MWt@aFX2LoW0LE-?#7Hbx+-@x9a@^p6-6u z)4kSeYId)%u~Gti7C+JP9RhB-doy6(r9|VS@g{=@QyfUwfDo`wC?X`5rEbD$@v$SI z6{p;JmmrJy6^ReX`rm@Lph}$4Lr@9FNjPsI*N!6Iya7s7;@Y>XQu=dFQ9H&DxS%)% zZSH0EkcFLbO<~?>jiT9UJsdit??A1lt zoxu*Nl;-Z|3?P+J&4fsqDGMmUN+Nhlg3}f4mHTM|FT`sm-!oR-XB4WgG;3)W>$gf? z+ImK@_jxbYG*1QFjx+@O6gXB0m6zi58#8NA#rVuAvTNuuBA1jbG_D){YDHbg)ZN=I zZJ6t5L#(4_6ly%aFK$KZaI+j{wNv5h&d{wT>6u{6anH4muwyVu@30v@tyrh>N9ZlJ z_u4Mw(mmc{r8FA8v~ZA*%d}Z70@ODmn*h9{6?1;Cj!26=w5WcVsKLmSU~qFZ$4(Cq z4(~RHJSiPgEO$c0>wE&IIL_|TmSMqM5&J2O>jHor;QB3K!2=FE%0?|AhFeBw-0yW* zo^zG?@L+JHuwg9?A%d`sQ@MmPybakl*AOQ@qaI^!Rbx=O?t%uZHltr{@OZ*5ji$0f zHrodCK5Ki5bWS}Xiks;4HBy<-K&(PXGwHp)-{B#fTfC`vGl=o$XMUFj7tQ}7DoO}! z!7)tvkVwu;@tt6aAh!f7T(}vLtGn1K<9FCRICBqC^SW3^aFn7={$yEG+&jh3s03UQ z^!4up6NpNZGqlPrLhI7EC|RRm0%GW-Fo@wjC@y|&4Mi)yWA-w4?O8V-Qcg-osD&Ap z!kobBrX5i<&8j4r>#mSxB!1!y6HsZ?D7wR}4ZtV3w1kke*tv0U;h2u;(;KnRDrC5K znJDI)3ga$5BkgF8%kNKP)hbWqJ&`T&ER7I}X(g14X~}$~=`jf62-N<&!rtH(!&LjT zh@QuB+uj3N`yE{#hkwx}{DJpX5?M0ffMBNp1p9w7x&QBASN`W7$G?Vq@_)}|(4{7& z@|$76=<~|1S{#gJy!~$iZp-I`sKR3xxyqGW8!w$#f_#g^1q#Cd%fN)0mLmVxU7h(% z*5*^T9rLcQ_Y3$Q;sz;-LuZ(%wPvGDts8Wkwh$io$`q(9py!I^lBsSfrZvaMr+G%sY4r>lsg~r#m2Oy zJo3SvOlfnPS$u|&BqM6wQe;svf9&(&ndK0z zDtW?OOY=FQKqTR_n=Iza>G?>UjRxXH^vfsDY$6GlS(&UAQ4RQhfg>XJtJtr2=i6@lNqC~CX*en_QC6}GQkpCAp{+Wq1GujP=0OAP* z=08fQ1)ZE+Z2rsPO3l*F8n8>kgR7Y_{m zFb~=w9mp4i70j8?crd>adU(y6TeZIY*4M^l74Af-farGK6 z_oA|sW@GBEN$l!&j8VC%wb@*%I%3JmJ$UsLnA)b&6pr1ygj9~}?Nx0#X-kINVW?sb0J@(EbU*BrkxqG;*UuQyIwpf{_@i_)}GdTHp z?Hx!yQRtCJgR@V1^!&u}7}J9D(n22ETHK*7+hz8=xFvjYPjzS>uz$;C;$b8jIX89% zdYBRQ%dmnRY{Em#C5lRj?ucCpEVw$7iKZ~W5WSO@=yeFPkd1}_`TG2n6D3l;*wSn zvk$JhI|}-~OAw2x?oT86vcELOCx&8Fj93Zq78JQmtz@~SN-Ju<@F`&skI*IR> z%?NDbmu(z71EDXpQ9^bg!ctZ`Uxjq@D^$8bGn|#E3%XnbkCcEUQwJVc1w)KS=)nuq z9wjj238NCEKq4wGB7YN@LLTCU%5RU+MX0`LiRu9FVb-Yv$&sdd^pfo~^0yqP|F z3uW6HxlPR#$%z$g5n{HFJNI4iD6~aI0V`J$s#OC>K4AarwP$GgqG?IyhWt%;zkt+K zXxM9=U}YaQk3IX+lED;fDY&YgAoquGmXmHWyE`>2$K1NoSHx0N@+Zn63vW639X8%* zdWun^W4!`v>ZN8?A*Q%tI<*@(!%W@Py7iiAGM6dd^q)SMZu@rsTi3P$bq#S!Th<@& z9R33iiT^z?R5CTRR`zfL?8f=WYsd%C)EodbH9?l8sskwYU{KWe0!Er+hb&}RM4~C) z^5yI$(leM?U)3+f4hP_G3Zq(!BN&IKeWrP1$KRj5y&&p>;b4$88tS5wVS`nXO_5QN ztzZ+ml_a}I<7$>SVMqLZX)zjP%ZDu2eunKOxbb8hW%f*Vv%#AzkqZm&jsXp7Ey8!m z(UTB~Q_%ts^PURX*ZyLdE9jMtJka|l)qJQi6fL^O80bP_3$pJ>TZ5TGCsb^z-*ha1 z^x31-bE5Sc!4ffz*LxT3CZZ^9_KWP9n5%$-=9Y{u2UHgM>QySw0;Bs?+N~4q7-q^ zld=L%PZ@AKk^lP>{Kr(|57aSrB>AVWN3gQ49g-m66k()Shn0%7Dh2ARk0)&fqv2Ud zz*#MaUIiAP{B6$@Oh;iOZ)>0N-EYB=@uOw|fIfR$Hc)9HG`-!acP7j0CxCo%NbU!& zhb3V+D**fko^v1|X^M*q!d1cbjq8Fdhmj-}clLG@_SW$@OYkxbCeyI{(*5z=d8_pp z^Jiz#xa+WA=cx|Y1s)~q=R`Wdih~E>X78%Qm%diCeg)0hB&IB7!}Rx>N{RS6V$!p6oU6u8ZnZo%30cTsFl43wS|7?{>y7K zY4t3;9B1t(j_P6ba06}y$G2H+yS<3;Lpb5W7-3}a>$E!iqC&$0fd8|epI%Qh^u!SF z5r$N&YXspvKW)gCY!^mEt4U!Ayv=7)Y`A8Pj%JzgZ0bM=}hy$w|Qq zSxWsN_8)U$r50qXXT(F6iiS%;2`2NBhY=#bxYyW`j3gFwV3T4UQeByy`?wku0%chv z0!TfqoYB5H4P7*ygtgx5$B)fQ6vm4YPygVuV!N49wg$pPIoE?ms4z@{6qL+t!_Pyz z@hid*HK`8{4Kvq6{s5@aa>gjQ&c9uuBe`ffk(je5tU=+{5-zxlprJ{4B%s6#A!w55 zyQJI%$-#9t*@3ba%7N9F_x^9>{TVAxIunOq0HUrIKuM?m_mL-TXJc>ZXz65UE9?kR zk~mr#{-cIOt8F_0cBOyXG>+S*6bH8e0hbB(+^zdb1*26cXi5ZxR}Qx<|pbW#4k5Dc9ey_x%59fiB9j`X4*4*Ki{w5 zzliT+pY^AOw8C~pFLKRTV6Y>Mrs>RFq{Y1z`FC@e@9~t7xCqjrv#P*893=4Qtk{d< z?ZmI_u-@R6xahFn@R>P#6QJ@hF;dIwE;{HAibErjUv*r?Qihp2F+(pov{IowsQ&0^ z+tSKG-chxhAuR1Zn9-hzI$poSVN~^0aC>Iqr?YxGmwdFi^aBCMlFVV(- zvc9vnQt3yVysz3sX4g&;?nYjpNch#R>RRPl&2#1D+RgR~dOER{koMGRZ;}VSjo$IX zOUrA`k=tZldJOft^B$DHI9+n%y;&{qo^yGtgx-q9LR-PKDEk#Ek@Y9D#cT5mf3biz z+H{F3vizjDB!xuF6{9Te5^1sqikhnX(9^Ap=#te(0kHAGN|(i0Tv5n2?1DnvgsbFd zMnDs)34(;8nvC{jfz5>odiJSfL79_`l@*HB=8C|X`2gZ%!8Lo}MExH9kzmSrnydCs zS6Y*4=Z~!|WMQSrA!&Eb{uVhHEIu{|kX(u{$=rv$iw(E|mIulj(4JyUWG4)E2eLS; z3ao^61_u}+vN(N$VD0GFsK~4|85ls9ZI_z7#KtYMH2chOF%OMX2%DEy>@tfRkQKBXJK^&2Y*g2Xd!KLaw2>=De0z)!s%-|>}0D5;>qF;yq9OFyDGH%`r0 zeAc>(1NXZ4+-2xm*hk?R#u^Q#tldrnEzbh8xr3nwg>kd#0OQNf($6`qq$5o6_ea?>xy~lQ10{r8T zgdb)~R6W{5KE(COlafDseEY_*+oI`T0DC(++d{7tM^YL4T<9MKgqm}SeG$y#N%=!D zk6s>8$~i^0SIcxq)Hwp|o|}6HJ$fS#n^>akUiN-VWalzs5v=y1O<-~a4NBa#q^hJW z!P3ot!^5hU%M*|qN~Idl(v zqB{1X9Dkyty7RbJa;m!_Z$bX_SB2dP9VsZQ*XRDmhbGcPoaBo$YJgDNG(gVU@Cq-_ zq?L*)Cq7I&eo!)SP-84!e`t0(GR@@*R|(cWqWq2O1GhmQx}?46pmJ@yS6qpzT!9{S z7xEb)A9Mudi>q-0uTRVu8N@W0-^b4JrO-G!P)JxX!aiAX*nQ=oOY@K#?0(9?Ua;hl zrBc+#2pX!%yMB4^^57rx@Bh-8s5QnRa0kGx#s3VqR4g3r-2NEj{nJ6*Ctg~9Kmj3m zHfdo(SP)?tBH;`ivYzom3^JUtgQz3V&{C<+U%HOfM$tE=hK0`gXDriVK1UU90WZ~Cdlh%XCqWVhx7K$ezk#j@!Ky= z$>r^qWjn2$xL*<~fi*W!FCCXIQ9FKuA@;zVPE|dIuDnl*8^595yj%$`nLiK8ZFV6B zTp*8yy3Kht#C9FIkiCjIiet8?2=LOeCZwvh-q(;c@j*022HavCG} zFWPeL*oRW7a}iKDQMw3_kxB$7SXFY)g)kO(yTlXk*sE9v_c7GG7XW%Oube zh)sz|7d*D5sdnRsSmCN0K?B1-`_vR;gwUfVRP4{d%chy|A5EVxx#2B7&tpJ=FLt@C+mg<8` zxkeo14ZbPxl;ET6JykkOwa(LiTg!7P(vG7`^gXW-4~Gl5D9gd)39vf9_6UB5+>$+Z;_Bw^0N9_ zg`~z>1?b_Q7!ij*h$c+VvJD+IqwdXn?D6$X4lQ&M$frYjyI_1F&at4Yl zg8rm3STHc>%$;L@`4`te2sCn;YCw&_127y2{sSztHgt0O?*bF8G^_ky)2)z_=zib; z=mNipBHpyy1F)lkF-m24Qx5md600@2HB&b1P<|NtKKC2=n^;Hh%)AvfvniRK)}NzK zJKyPFJ`OM0zu3*e%?%xXvC?omk@SP(`E@%rN+#^2Ph|;@fu;Bq)X%*{+&?E-g!F!A zO)ct%IRLAtT539VP`&y)oidc%g<`CCz{1GO=Osu!BMk{@!RPz@)YBDkveqN?rj;|f=A%GCd=_6k$HvgQXo^$e^K0hp;rAxm5r}fD$Wn| zzg6i^r@JuiYsgnXRni7jrT<{?XyjsPZ35VHLoaD=3)n1d^8d2n`JZk#?e=v;i%Pyb zdRL3~RYWZkQXnIvrF&h_GuQT4moYABGdI~Ib;$w??;D8c!YK0+X=NJ1JEqeQpVO@L zm-o|C`Y+gL9`^RDqNqWB8+Czh`X z{kUWG2bwitgLl2R(*}`B6`4xo){zHSoPINS!lvAqiY|rp$!p{MUxRm!2h(`e-z0|) zd25R6Va!S22$Vxk-$h~v1GD0U%bD^~V+RfC4&&!6pIxhvXvIblc*~J}kA=wq-ntq4jWzK}hm-k-UduZ^JL%u;|0j4JqY}<|kU`izxIKw3wktd`| zw@eeioMBQZ*;co~8rdV$A@6vgnRQnkw=zJL9df1Gf8rX5oFuQn*&+!LtO83fSv!S{ zWh1q>#bARDPFYdx>1kk>f$1y0`Ulf1H&y(q)hLqGZUzpw?Kj(g`IoU_Z9ryH0-%&Y z07}V!n4AAkeEk=5l;v;!-qA|qQXql|KG{Q2m`}9Vn}QMrYmhrA@DX?<6m6h9u13h( zqd9ZThj;3pNGQL*@Jm&lW3GmAgjT38|LiW;)6>Jx1*$Tb5rr7Rm1!F@Sm;j*v<&&( zu|TzDFqSgG8nTt*ONIGbAvM{3)<8EMU*@%hS@jCHzZ|6Kkj>__pALjyBsheenc$}WRx2ZsptpfFlRC4Kx*JV6@+ndM0!(BF9_LQ#Vpw5WE z`*;sTR>N;e%tcvIJ4eZySsDxgZKWaxAz@tkEe^{_0|$Sgpu}83kTY9J8Z9!gOk}P9kuJy zs2y<>#}@j+3G?1)c9LSK-cOsUCr&X*5oxO-YBO6S>hNn%lre%d)3p@F3@wV(@xhgj zNB*;YiN+CEr^1wcD(S;)ZF%f+Rk>E>dcrBj%0*F7PL@r3&Nq+9it|e(lvQ?~_YAu< z)a`f%E<5hZb=F;`uBt=rlV^4yYVhO|lA?rKOXd~|!i++Jfq0L> z1(@$Pnr;&p4dpO(EhQO|6~Fs+i@(xPYfDf46A&Ge3_b26kb@#kILOjanFZfh#ev!~BG}Lnn@%hvm{S^UJ zc=e=i{wf9r(db>{dK!9*tnworz{spnqmSbP1Lx{E0YQzmtV}2xb)-d4$~nZ73bKct zd8+0(qh>dqoV&k%Xc4K%S`eiVX|nID+3Z%4R9qPI=997gHeH-U%18@|sr6_{bRS3{ z-MTs{kYu6|DVXmxW@1C}h7(uOmq}*Fltu#2P_--p&u$c z3z_eAqXasX!K2;GgH%Umhnu1NN`O`j)aOnB%(bDIKOKh85(^oB8m_ca%Y?8`-_@&X^AHi9r-4$9%9eFn+5!4y=c_UYF@4{0xpODp*KkvY8PDm zq!~Fb=}#0nC)pdDl!mX@w@YY6DzE6yJa7-JRJ&s+}_++-wt)NfPkgk zk7b55yTpC=@5QebuhX6R?vV=9fkbi$UxDKXo~3alqdy}syzVf++LSP9$KCV4KgBjl zzDpqd0{!Yh>quJ&-x{uCqACRK1_Da?dM!*Z7GER^vC4P{S|J0X3T}C~X5ccEzv8Y- zEgQiP_Djp;_Iuo|2Va(*j_6nFB*-hngCD=~-nTk(h(AEMBwg%U_TVZX{L=84QZ|%| z_rY`azZi#!@8dawMld{*D&f=_zLQ`&Dg^#=et@Ttjt8PBHb2{++%J|0If_R=+omZp zhC5es{_-wFuC}TfQSx1EwBm1)=_5^ z?z(zZv3MxYBs&4i*0E|Q0Jn%s^o3ThA+`*&&;%6!kO{^sBGoJ)iDJgH5aBWn7Mifk z+1@%`tUc(5lrY&>t0zQVQeISr+~0qB-TxU9*Jp%vbO5ePd%$)1ABYG4Q&0a1$NpAL zqm^`}u>n2#)gf!$)#v!M4f*Ekym}54Z)H%_AfYp1T7N#d1Vda+^T{99`JWR#Di-;% zK4-C1-Hq6ZSjBDdPgXOW02cB4`{|qf7rQz{qQ1f}vBEt4i(h7iRBn0tzWK2V+2~_m zeT7V(uBOZP%=(IA*i#tonX3Pg^iJRfrJllNsZXYB**qIMT*1AFx$i>WT+cD9DPeJ# z#L9%&twRoC%6!YlR<0UY$h1qmWMdoEKwi!(XL9nAneFtV86lFPfzsSv?Mouv=C03i zGRu+NPUv!rmN?h3evvA_dc{WG|8@mZ>sDb*tZR!&WZQhe_<2djBkQ!OPQI}_*T(rH zW;NpG3v*C#y$BR^9o{~e9+K;IeNRE^of7?X#9Ytk0u**4h{-{KJ{RT;hK1=nCd(Zkt|d-gC_&YX;l}6 zZj(HeUlxm2KXed1AKbHWWYKO>F+4v=r1h_`DaC3@78?M|u>i3A{{)+u{~iX)y7I_? zFj!2vZlI|y*1FMFX~I7nItzpf62X2mz!EA-INMYhVQSkZZR{-IAO73}>5=#c1*NO8 zQ^f+?IB}*O;7i?Zel6qs{_%m_M_k2K=dkD>tc{j}d*rY=fD7SPZ?vBi{02Mha$IQ< zNT_f3g8^luKgWc^Et#JEaKC1WxBgPmEFb$JxRtnx>5WD+>``@W?gHGi58eprU?R5Y zF?iI?`1nc^_b#=T^7GlFL)Vz%eC?_D8nojoJnpRW*R}sMK^ysRAbCftXyyn@PT%A& zx8}r{VwlZL{U&AEZm;JzOIO3U2ft3hm$~Wr9*+%+fccvg$lq;PZ$we73T~jRLhk9^ zVxU`_ycdXLmmDU>cbI78)Qk1At_W6q))mLcW@WGQ^V`yb4-cSgn0V5d9+sxhr*g?Q z%2@}k8EA@w+_Yei74vqTjGJhMEc4F`Or5bmebQm64+9}z;_%{9r;SzA-@SHrdcMXq zz@#(bc?KTWlMI5P^1%o@(GFdn8HAa7F0xIL!<&T&UE*AE5E5ru=J@#(6F{V{7$r0@ z$X8~f#=L{X>!6s~|Jm zMcPZJ@XQlw`}tpVDoUvSVq}0Ay95ki1pdG9o3*uwshJ^w%&cJNzh1 z@Um3_o~mjHA74{wX+SX!gtAyHDf8D(h>#Yg`-(|PnurO8$?JUz~oG97zZRVkqeL z%0sl_M`ZeZCzIsf>j#rIP`O0xK{iLB_7H9_cbMYWVG5*C2Es!%K^UoCNF#~}x9ve3 zyS{{m0h0wm_c}Yq7?l<)IhBMkMkS%&8? zukQ`lKfakJvK(@D7NmnsGd}YvKWPZaw>ei94f*{DdkQvqdM*|RC5_sU6H*n z>8V|o{aJI>A-2u{H#@hD9euAFf>>&++0kgpja_nrwCl~}R_0SmSz?Au(PPWH>?%5_ zvwjP*jakwl>Tj7dJzJ0ikQ_G} z>-I3g=77#3SQ(FD)`*uPmny%U^-FRcW~;0}V6E%_%o}^e1R0fT#Z!BL$T8He2}AOz z!GcIRS;p;TymwA}d6`m`~X`8-v*&rItr(@3+q$kV-1q~QrQ z>1BlBrtPz2B1zh*{G^YcTD0L(T40CO^<%M{+aw-ca)i{I z@{mZ+Jnj4v4HMr$c7&>diEl`psm54z#LHcONS&!?gq^4-IX|DKfT$2!cZBb&$r6Kb z{e$lEcWZ61T;F7++cdZk=g;DNrAH>U*|H65dVwC>4B5%#PyQo}nPx7V)|0heb}YW6 zBWCuMZ$Di|=key3!Yt^1I#;+fEUgr|O@w+57ouHdP!gB2vIImK=*)LXmdxN8A8u=b z*O#<=yyu^KiTgtK-?85!C%B5clUuyK%8mY(BY(3FQUOwasD+vLjW2s%m9-=4)*mp{ z+RfW013v>QdQh%=7|vz9A-)2?A*&|lQws4j6tQF%?FWu7z>ODqJ`45zFcBBQzsv5D0rC&X6b=Yz_v7I#N{|NQqDvCW|JG7_(eS;Yh zohus4s8@uPdr6XJMI6QkWzpoqjX(~ecR56D&LsfC*k=d!JpLe$;U1N5EQ2t8G|xDb zDARO?g|8h_(H^BZVHD}glxM2(RBiYBFD-&Um)>l$7i4@3;BzlGf=h<7Kz39_f36OcSlxUPxyEha~X|Koh&&=DCH zhvAhr%D&6aY{SpDy92ZbEvIDA?++6C6$ep*B?#$zqMvH{SI9M5w<#zSZlPOS27JeD zbK!7_{KJ?u90ppM9#9)!DjwkFm`ZYIGy`I)q}gVpgs&ikqdHzq*b*MNq%OdixD>-K zo6EH0JSh4qT+FVsWhUQ*gsXaX=nED*=kj+>tXW9oc99S%KJ{)bZ@bMb6SvA(<8wPa zaBhR?b&fghWeC=hk%xZ^6b9Man6miNrd+i9VgYsd&Oqh+ zm(JlI-cJpAg02DJ$maeJox{JFK>qFYl9hFx78DS8(SiN_2#N!C!J3m}rL^?VopHDy z!yg5?uFB+rYDltK&bLUuM*;M1d(!w9(%glG0IuwD8+(2daG^2YwJlrcY1Ve<%dfXh z`7f956@r7raF#Tc8uJLFhbZ5uF0c|T$}B20RBwX_+YLEWddCrJm!B|`Xx;iS?{Fvg zJNp%5BY6wWl}+*wAK|=q5mLPI!BBUbctV21q3>dpLIAws-J)-@$C1{0ehNW zzCOvi-S9*-V>%MqW73}pxQpjh78Cc z3ZGXj#EznWLoZEa$I48q?S7s+9ql>h%85V|?y!Qx9fSu>K4Y#hncfXLmKn@EOVc=I zdCc%f^mI9xtl5NnHB(_h6LGi9_4=?7JAU~FDSO%Be|B#p{ivCf?Jm7y8ZXEe+1WN) z$8VGSGEN>xCMIRZ>Y*gj z)C4)&sjl{DD6Wth6I}J^d`b+eEe2TgwOxS(LDM=eI-Zs-!#vDK?2-GRHse%5ZryT> zB;V0QaEsE2Rv|rzM%0v8;+*yXTrhP6fN{uvlD$B$Yq#M>bPDrbfqE$Qkq@Mt4W9@R zeSnNJmqF;qZH4DjkQ2&;oGB9%l3oB6@d>MN5{)5?`WGlE1Uue=dg4nnFHix#{3Z#7 zXhy@6gHSW}54&jEqai1{22?WtFGO zpz6CpXtyY)R-S1U+-o)2Syla)`;wD$((5*4%mOm>wn}cXoS$p3gOd39MaeGEw@a+P zwFj6jtn}_JsB&9;yB0*38vsyRWh~#)Li!ZcB6wO@lj0k|cC%1@Xo=y2gJOX*q7ZZy(vc&Kl=c)J zPcsgLQ?oXe@C7atkP1Le|RXoAMuF`Nuxrbi0Io(W?rvOnoh&sN2i?<@e3I zB81KV6HnF?MhTWuOO`t zZUg?X-MWyk-si){*yp3-Ri@qfCM@KbbSBr0`Y%Ip#ND1 z_75psi-?j?kYjSBk>ZkGUVP*#5fvB;Say9_;b)4ovOSCzx2>_#ND?q+ zI!8(1cQT8SO$P`!)2&2@T*3AIleA0D5LL4?z4Zfb9p-k`m56-W**PvNjS%+Hxm2$1 zEIso$Xje%F=NgO@_hWVTpvDn)8N|MIF80-SW6=9C3J=sj$<$%3IY61VggfEeyg7HfJ}WbeN*82*GS4-xA!YZJ2Q`B zbu5im@tX9&GOn?3)jF)YQvN4&?ZwgDBHBQ+&1_vY!^Go4y}#kvk7-yN(L!s7SvQI; zo%a!s>j;W~^~IzUU?tH_H|Kg2P@`Wtc+~5o&|xPk{?j5SER`oX_Jd_QS}9OEjxjN{NJs#P z_U?*zbykZ-3F}M2T|p=qySvHgK)J~y&OAWj6N~eSNu($aq}bE)IN=%_`hu{Rr=GRT z84?T`lIY(=e*TMmG&XNL_7jjf0)VUM?|r?0n>qhf3d>kJIscRGsIsYq3=jz;17jx~ zqd}tAA_@v2Y1C4n-v)@xqgD(%RHIDND(-ncfPZGcSSOJX zS>h=&FX?QZ<>buE5hX zMK_c-9jEfjzow|qeN#Qgt%lA?u0+epG0jfF9B$I|zg`lIn;!+jj0WAn#NqYjBsvrd3Nli@M)&xy=p@nPSVxgAXZ3Rc=IYh9qx8y zhM;7nquJY03P$G0zasQK~%z}T)jjvwZESi@cD>&gzj3XS6vt3y)$cZK)S{3+rs?9$MQw}pxGh* zwC$%4k%vu%Yd4O`3fIHNp|2;W_NAPqqOE#4?_us}C64D~#8SO^_Y^iUJc`PR-+ zi85S_?Od)%#aD01w+Zb+d`UjjZ0`*U(lnBqZJyw{L41B8`c*#99D6v2=n^-=h4fa) z?vrw;_ogeIeh9iKwx&ClK4m*rY;k*(vL6-8v8qg>(~I{oKRMjU?GxAp-UcFQbx!&7 z&d=!vT+JY+pBC*9JVVj^hwI%KK=t-u*%AF`EwZ7_AqKd^dPGK5?jh3T4{bUvJ|FHz zTc@berf*bx!Z7@S2Li+9T<1q5+cW1u58tQGhC!jG;#~=hQb@=oSi+SpYE0k`SWF)Y zBMD*EO=ed~#O#xSXAmmk)ENR)iv0^w9nN$A&RH;eD21p2Bvau3uWN|;9~qUbaw!LB zk$#jU*=nkl`vnkapf;l$ir#3^tPzDvDjGwHeaf~Pcbar+KMA>0e5P51XFPx512`wT zg4hHjG_1F0JN4XWPfq3b^812lhgU!0hcJ?#qtslPWw6<-}Rqkv06V{t;l<1qZWxeQUZJUOrW_K81RHn7K z<2HR8*xqNAmf#rOTcLB4ZIAtW_T~bM6V)u_Q9E#eO=a z&d2t=<_383>A`KkYucxfQpe6PxQ(*f{uD0GOL-GHM1Sl%iCYQ1)_Xi)rCT>0&mgOn z(WqhCWY9-umuZ}^{w}Pk&93tghKc#tTcE+I^9fjD)kZ@netXI-sa~USFQN#8t19f+8Ik;8g_e5}9KWU$GREFqP2M7DG-vRdelU+0q&V)j?sN1$!WL)~NmZ z0CKFHgYP2;X#-rj0dNc z7{k}5E(V$$m?*;p4qF$>lvoU=Be{#!4@VJdj_u!?+QC^;L_&woQ@eEq_j`uR15TjU4zl@UfMqaat__q04km{lq= zM$&nP=t{%^v7o$TZj1>ZkV}FO(=UY>Mmpygq8N^>ftm@-I)gevEz-Y>R=hvZL-=us z7;Bv4ehiRtW4r*dhCGepEGu-1JRalW-;48|oOjBtIZOmKwfkr8d&9u6j5=Vujfox1 zB_e2%eE#L>{RfAQK)^)O0utbl8NuJzX8qgSWj2*^@kD|e<$f(eo+zt)ZfGKbCBEP}5hdh`rnJ`Qw;|Z6b zb^%^3xaX1Tw`KGLtzAFoXTFi!x2+!LFMYT(Bf}LL)o}}+Z49ObS>K)qLLN&zBR>65 zC|iq6dMB%=_c($TDZ79LEAOa3ge;abiZv|Mkt6~4K0+XG7*!BD>0J2bff9;x{Q|3T zsDB$PB*|BtMW$qzaI*lFiZ|X8uok=VfR=guI7QPCBgRF>}Wp5@3?u`W93F%(D^H!^F#%BuDV0S$l{|_v0Jgyyk|B~Ce32!GIQPRL!BR~ zmecqNbc!Wy(U&{hxM^cltDuFW>Z72UD0&T!;F<${N5so;op_3+WX5B^55ZTKPq-0> zbJ+C7@HDrX;VEN~OcUjNq?gw7-w6tjX$iBk&{uj08w1>`?t}j;AjbWRs-0` z{|Li>hTlJpk4m7nO*(x>d%mF7=bfW?AHdj7B9xGCS9dnzUD(|T zbBbfLoIZm*E8_f|qLa*+gZ5tYXs4g;eoE(VoYnt*PwyA`JUpc8ZAKK_+OH{rDsST+%_FUY`)J1L_ZTlczB4SM{y9hSd+~@ zHJ^XaP$d<15tYA;Q#+o#2w5a&6vSM#@(#A-C|^esR=Ry6B1J{cFn!7b`o$w_M0Y zN4L_YZ=D5IzZY{m@+o)g5yr3 z$`iu0cg;3aG zD!-eQQ_C-Wpj_D|0ER*Jx&FMuyLi>gY4fg9e1UG3O-9&RBVH`IoCF@ZCz`LX_G-kr z#L+@}1-nTNn07?XJ0hOl{>G1duhoEj}0OHC;P`P>{$xmbz0G-_xIsMlQ zc*7e5)Tt%4rt;x}2^gngwd<^_+MsrDV4Z_p#ju9PHY? zZ@=fpgnJlK0MFu4B5ABEOC##^k_xpE{ee<|2PvWF+z0XsANc18!XUn`i1&ivkOA=M zl8dAO-d@^Q*3BV?TTUfQqog)m<*X=nz;ey+=HKy=y>u~8&+DK;dza#Gt&xa?uk6zU zDq(9adgVX=GBf)!$7K?>J5O`G939qD8qd@$e%#JRKlF(QHnmc4a%wzcT;^gHEesboN~f@wMF0Z%T^o&aXmSREwQTLkGfp(hbYso+dEJmNfEG4P) z{GlKLg%ZV^=dxyG*ajlDY`$sWgww3%QHEE}O~m&?%wVBdx%iOlzUoLV6O5o zwQTWtrINWO#g)UAVt~sj4@yX+VIJ~AngBiu0zL@w!2Yg+Qv}sT8D@l*%vJowr$%T+qP}n>e#kz+v?cq*tVT?oIdkC?_PVa zwQ7HRpZcoiQ#Jq2S@*b~F|Ki;30C|M-N=A0&NHL=0Uti&p}P&S@DBRJz(l6Qc>Z0D z3tq+!3J2U4eO!9h1Z|8A1-_LJ(5>V>Jd-G z>~EE>bRM>I&%B{J^gkDB@wcF2Gx@{SuoJ7fRywY@(z0R(B-*s-%Eb+*_0?g8@e)*- z?;DuG8XwwDFsMuR2R>v%_B71*^BfAV4=!{rpBYpOaQOa$nNTlhi^y0AXY+TE0N?L2bj=t_24 z!YG*di>W8%BN@p1fM&e}4|$XB%<=>XdYPTV9VQGw{>DT{yq=w~q-Ks#%zh;^ghbbL=}zHBkCNOMk^T)y zB#1mB@wQ@=wQPZe`q{&#WCuWMNu5Yfy#;p<%Q-`P8!dDHrSe||&w zTPCAm_T|vyyQ8#6cYyK@ba&EYwx;T7wl( zfL&+%ayKd6MXvSCwggpUbk;sL!pt*x{Ps0a-_(&^ZP+w&E7?bPZW4(CBikUzLQc1s zOIiAHwq|+;o|;6bt!kv6X2W0ZA)h=EEnA=&AX6!u^O!2&(%VR)%F{}-GH~De$W~(; zmi?6?)$i0wJEI0*TUKr&W3I-VR+!kfwF@zh$EVl5Q#X5#-v5v+(DBxb&IuZ}==^pD z!S&!IzN>Y7EH2uor~}iu_y&R>9BB52bJYk z00`vOt*dDIUe+3dc!|nY2Bvsp*%9(k#^f~yaH)03z=I_BuDRNS+VRdew+DQwPTe6kgf6j_w76v6Z?f+4dHt1 zsvC}rn@Z`@wAWg8q77@=5UD5=Pr7djT5@^1A|SkFzcGP-3$v}gSD=qSUGZu{I?XuG z&on_QB!jj)!uNIT>%xDoD)B)_x22%`M*6XN_(2<-t2%l7gkckBnlflmqb$huPoH03rM}*WlI(7=GFzj^dBX)Jr7&9#VPbHF=hh8BopgG{?8xQ?C zk|jxce!w+S;5Gy&qo8HTKvLQ8&LWSL$0N7`g$$r`1VG3R1*ewhBk)@^_h*XX$tqG6 zM`RM=9XTidWXDFwXUW9OD+0}1P|NL)-j#pE-J7qW59XIRFwn;D-m8Ufz z%VtCVI&)aVvez5qe*WU_seJDk;urYU)XqQH$3WM92X2RZv)`k(NxBN!b|G6YV5z#Q zVRnyM7=gV|LT5RYkSX$wDHK7&rJw@WtQ@I7Ov@SFm}?TL^$@#hHBQ@y80*hJN!I?l zhR&R{rc$7RfFiK~R!IM{ng8#uAz^?x{HDQ@FgfzJo(zKIXved>cZB28%l9E^rOoS$X8H3sYOhlPBD|u`ZBl6~1 z8*~Rj1@#s#+u7XBt*L9RdB?n$fyMcT_c{B|$6t|Qe0Tdn2;Vg5vC%e*g?6u~ecCnq zxnDlz&~iA+_jTLJ1O~NLH$&YY2E0+b)kX2@ULC^0wnqn_A8xIMl-v4DNCH;JQB?;DbHi-&1(J@qy*3APpg*mF@fT1473r@f8w= z(+LiEI7)W~!BDynSg2F?iO}dg(uWBYZ_5KmRYV+S<7V#Poj+c>3G{cLKdIN(W|Z$l zL*Lp7@{B_CmF`S=3U+Czdn4-_w(FXv*fG!9KVMPCE1J8{)2U)0ax@&D!w5g%FO0{!h;2*{f(#;sj($ zT4ktdO}ZoWx(URQ$amC9B;<7d7Lb4_>T(9%H76!G(zX00 zJnte~Q=AGo?2lD&T$LH{O;{Vz;+?V`t<@G*x)p`8Vq8`kRI#5eJ{nC$*_UeMR_-`& z`49{kLY`M>g0HZ=T(tH1jl2QW&J7rBN>64R^)3?#p8ie{p37W4I6v;mcu(m=xoTF> zbSpM;m=2i4fA(>j##Sh3a;n0!%IQj>QHzj-@@7YKs#^YvH+(*Itc)cQvmwlLpDb%p z&P)q@%+cf7$!hLO%n+$|)?XZJ=)D(7;r8@FhM-;I$FkRzv?j#BLIY_dCGCC06`6`_@0DCV|5!snBP&- zn}ar2f7>}*kGHeH=pRKzTl$ku!v})Dw{8~#TQ7vbYBib;+P8Rb^d%xxJ&69-t8yA^ zD(3vH@`ude%w2IPZ@bbU!!0TU4;|u!{dbh)W2nZPMh{I)5F}OWvbnFhhp4T2l`pO zP~;W{awJYZvYH@^?#wrOVs5A>3C62`D?-yYa2?vo#e9%hNUlHR6LmTO9OHlm-ocbxz}&-+KxU~-cRSUdEU{H*Xd148pN75Kx$LSD#azW*>>?YEY_1*NQv?)Gq0worsmzH(k(?G{c!`v> zow|;_AWs_A8yj1CrH8T|8L!Z@cxf<8Ns}4B?Z%uilDaf1b}G~0tet|TSfAmC^riPy z6ZMJKI71%pIPlsQp59-aP&BmWPmN+Qx!{U2o!8!NtI{n5XV~yh>N@&zpAC66nBJuAPVI9>ibFH< z%7L<>(fpUtk%jn}LW3`0*K^vWcY<0_Lub{u^o~rNT_7ZtY$tloQii%wp zNOcE0mu7?_${XA&UmL^L6+pvfX3>sz`HZo1orY3zeM!@9pN57?2V$u-YC&YaR29U% zxk4kP&)X%Qi&gGxf0Pc$xI0-Wc8byk`Iby#W|`RLM}JF0mr3ctoWK(x5~2-8$N{Qk zB+&IBYXQd?o6E2`%#G+ZClsJlVXvGME^0cH#RF}I3dbv8Qi&<@Qn@}YF|W~qF{F{^ zaB7cl2EhpU^QM#G>jlxCS7g83Ik1GC?jP5(j9R6mm(T2P+b&Y8oOMFIDpN!aE}dm9 zTyvm+@Bb&ftT^|P#f?BMA#q7*O~?G3+rCnSZK#U$GtZMp+8lP()_Emd{e%EI^+6?l zpQ~O#rGFW<|71hdavf!#52wr9IvtT{NkV$m50ZYT`Zl?IyHU($S~FCXdQ<2raB$rz z;o8m|(DU8r;z-%HEQ7I1(N)=dvvmpB*r^)|jGYGMb5Ws*)aK1N*lNv%OT z=RYhlm^!=SJ-Z^^=K{4yYr=E-Ypy13*4bKpAsYqB`z-IxBW?ZUY<3Nhpmy~*BC1Kt z2WpCI9zAiFp-IabTng2Encg>#H^n8&%RAV;y^P|E8$~`V7lmYGq-cBd!L1mOZ~H5kyX%I21O1@mfUqw~ z?+6Hd6LZ=nCq6fmlphPYQcOq{fm&|@t}93`FT``c)vAPC+V$dF_~Tt$AeS(s{v}&#&WE6$P4or8&VqOyBD* z{A3G6%6r$Y@EpJJMz6a6hs#~G5*CLLDqhOA9bq)neK10Ks2g4K+AvYdJ*I@>i{?G0 z5^8WNPk8cSVQ2n!DJjMhJ%)YJADqLL7|fJW(X_V3(6oPBq4=7rdshNl+6%z*ucXue zuC@Kgg#8Z)E4m*ZmO+!~aTPF80>$3S5>#8DvbG7JD_=veg@}icU+RY6hega)u=FI>BO^ zEvFP##y|&V!f?8hqZFf?uZ0sBHW~m4w*nNaO42vuEYW;A1tnC&*9wYAn5BMujIs6T zy$i8IHc(tlC@w|M2c>smKi12i|INAl)-qs80)X6^0%E(l{?~r~A3yr9*n+=Zm<$0h z!#~zX(f~hS)UWGims5@u=x_!Ff%smR@_iK0gh(D}T0&Z#bl4{?h%y!vd}#hYk)nuN zpPJ1M!-9xHN5jnwfcX1Fjsgs=>@2h)y0@Xm(7Oy1TXPX6kA#;uNIduJeuUEN17mlahY5d=Or~VFj3nN`y!YW6OlN z`hC`5*QvP1!z?z|4(_&IiGqy}u;T7*C~9R?uJG2|{#~Fc zijuhRqB@cu2~t~eD%2M3h_=F1V1&FyT{^@ZLoEDmmrV_S06EzArp<3*M9c)Ydakj5q>LhdN%Q`$zGJ7ZB7C*Fi^e$ z5q`}UejJ4J*vrc_WaK&5?#<`AwJ&3x4g0h>PbZMeok4?{N=o9-1h_@r`8Stdw=MjX%Vyd=+*!TJs*_5Um23a4>r~p_Dh#1F(|Yw>fo-_~nrJ zD(JZ09$8&@Py(^j4R$ZvL9xWTN~z$nfw$SpKA-?z2GiwgU&7o zrf>lH5x^%H@QB1%Zn1{gjtGu~!fDElQ5}g{MKl7*gmr2hoDPF12hJcb2=m)lsu5DR z&2A{`lJ3|>+`R9`yZe>fJptf;0rx9HDx(6GEl#L z*-cxc3Sq>;$C9xhAnzvHn=XjHioc@&^^5pU-H@0lVd&*Dj(Iy{ISy!~bNtt6hVxCw z^RFEH9ezLH*^we*DGk=aE#%TY&i0dhH6h?)5{-anQ*z@sX1fw&yb*STPm)6ziqp4--E6T4eUoN|23} zy>d({Y4jves=4QN*({YKX&l+5l(3$cgRd#L;Zt>JNgSAh8euRnbW{X}>td23SIFa9 zbCKe_1L%SqDB52Mm{>r6t}!nQ4HrKyOerl71CXK+-s)&1PYFC`xp-J-^9!17Q{pAq zs6W5^7&m11RjR71g!^~C8f}+m9_KmPIZYN?4lhcV^kNr^7fJUj8gn<=V&3#E&{t2Z z4tp#$_xIVRHb9+HB0Eg|%{rZeX7&3;txl+Ga%cJEo!9K~172%L4nK?!`L*`Ruqh%S zFAY=0R%$2PqdC)`9pF~P?Y0dDxb!##4JXa@nfCmIt}&Cs_Ml2zH6AdBg{^Iia}sW_ zXlfwm4#}FZ)laLX`e%&$?=i%zJU8o^wub03VUl<8bV^iH>XAeB4LLsl4TYxNF7})N z2s1JPuZ(|9as2Pzk$)e@e;mjdC0TnECd40dslmY^g_Bz0{s$1X%+{zQHg+V4Zy6(<_W0u!Vz*0sDk$+*j6KX|=+*l-8>!DE{r872$r zp)%@*7l&naEku^%i^mOX02$Yu>V_IcngBJZXL6aWU#i7P-_+4;Mc#tKlot_A5M^Uh zefbC>!lHueJa*fueJ$le#RrKG!+Ye}IuOr=_8>uKSMN807(LZtq7jjtYCpf0(R?$c zDU-T1CL`iQU44Htfcc_?IRQ1Ms@|9edqtzvT{JVRl{!>^XTKfyu+y=UCK=iz9LcXy zi1}4AUD~_!VcQGmtel*HwB=3}J9oluuNgNQz_?;&PKy_1*bt14P~5f}IZE7GgW|?^04Jp66Qag2$;TZMV#qD+mUjTnX3y8IY7e*; zpZ*=N|wu0M5h_;Q3!#2w{6y8xug@5y1V`*h;ih4!;R-f=A*4>`~t-Z)-j6S4EM?e&MTwCBKSg;EsM5 z}@j`M&HF3V1acAYMYU0{E@{OGg4Zxp>d}Zs$>nA0>du3*ru@cI;z^f`h z>21Al*LbPc`2Bg#`vc;DdxJG=hy$R)+*jonSr7t?o9W<5Grv~D&j>_4_6Ded&LbEdfhom<+jvF57&l*4;c6uE0H2ecjG&ik@;$*P(~0Ej}$- zu9RKtokG{1$-C)rRZy*lmT9NZ38X5XPKyTChAfa=%4lesMsm_^QAM&jv#Az?BPrX( z8Fjo+1y?)?`%h@=0KTJi$VW2H6eSJ+Kzp9vcm-L4TsgW;zr~Y96v*uCB?KA zgSR)?6qu2m5+z0)?2cA%VP72E946TVA6ubd@a>XY?%d zyP>jqMH}jKZK)Yr##(AORpTa|s49a?chb3&qG*eD9ija$(K9sTbH8G#`A^o4qTj4% z;GyiPtUL@ZT(N)0>suWN{u)Hs%Z?AmPDwiPZ2F{f)iz+o_0lL;_bg5?_Nc=^FfU}R zWT|;QCx+v`M;nY4D$#+M|3paSv@>8>?U?1}R(4vh${N3hwXzH7Is4MkRHJ+3ZHch- zxOgnKfTKxM?#;u-#a)b-w0|fM&xWJXF`VioV<;OLNL*EY^3WMzVhXXuW61P?V#vbm zuMb7}BfpgKf>tW~fz4pe(O3g1E3=E0n=U&MlhSUPF)_cB{zB(#zK@S5s>Y(dK;*Wre}=(c0RWxt)!n?7j^~~nU`DcZp=c};f}AM15O2So`@24+ChPX zPvU{{cjILizpM{Eo*+XlzQKejF|B~03~vh$u<@xdi*9buOZ-fZIUiHFHy<-J?(2Eo zla*4*eemp~Kz!CF-l;WI^SJyNg0L%&NP3f7O|8z;6|qWZk3?%p|1u#jVS45D;17OM zjAUnP3nXk~C%Br2Q8hz7k4yY@zEHklUy7o&?+(u{LcZ=4*Lik{5!a>fk8`Dj zu@oaUs78o>z`z?2;+hZY8Q~4s#fA;^W{VPh3Semy1AAL=3lt1l`+p*qaE*JA6~Q{o z?<+>{Th`hYd{RX-wIvy*=D{@aggqFh9(+hUh%N5B;z#7f4PBeoj!$889jo7sL7Y~9 zT~6S>19@*Fv|ZYi8!3mU5}wdEJ|SKa-Yuh%qmw%lP1Ug<9$%2#s~6vXuf?eQv|i=@ z8G8LX#b0lZ##{BV&OL&+El%~UewIiIlIFhq@Zr}ldO0W)ZRIDj)Gv!$5~e2kK>Ut1 zC1x4Pv%e$PNRN#B0ttb0%j9*V7M>~GLO`(7@OMvy;<2IjPapU(-C?W;Dg@o3S}h)mCj_+)8u zu0FBjb1@}QlzR}XdZQk7U_b458)n-QRI9%Q-x%`?NJoAiNPPX1!0#_+k^oR72qBqdTdu?PU?9sI4nQPMyVltc9m;`Z85#_Xe4jiCTYxjZmdmQdWfVj zxmAT4r^ZB9X=kK$7HOv%9$h*uIm1y*&gOB&xOGbnJ202^wy6YRKJb#I%Pj?|i=^2K zp!gItet?DMItxr|hjyK3(3fad$tzDLk3-S;GkBD$7M8EP;(f;CHat$Kk&n{xs)u%; zg)zbEC+7Lo$_sWWKe)Pm_CVcKj^fN|Vtg-}R?2T|*JiD(wN$raKE^DpI#v^P=M6K` z)9tYM^+Rt<(aDW$HDGDd?3Ka1s8-P#EV`g4z;IkF(R7LhwYKu4urNh+!#Q#UM)`8| zCxu^jQ-#H7U?k=~^*UmivE#V+;5QUl_VW~!M4{MeIsHU?i$Gx5b7X|cSNWhw+}x_u zQ8+Z-_F7`N82Z@^z}69#Q8UP z#4k#rBx0Gu)}VFL!c)>ldaD3!U(D%FV5WV`bj&*uBB4iC8MA;LY(>; z#!sXpmz4Wbdd&giaO_Yb7rYjw1Kms>5hogdLly8+8tc#A1J7fY!NF*SoCb`Fyo)GU z`L0HAZ?#Ltz5-Z&d?I6PiL>H&ZSs=ghT*+i1q~uJ?o717Wfo!(bp@0uQP6oLRubQ(p1<(MMEzRu=T>vqN|3MYdfYMf3!SZEbVVrW%1O=XxK}2Ku z6^}T#8Xit;C!#5?wggV&~00fQyFc^ixmkmo8 zKKgkZ5f}Hehw&i(Ns07;;+q`#YV4aF^?Kkth4eu2QI7mj0mK&}pR&8~A_G)*r^&=a zh3QN0Up922>@gq40!-Uy%;%$*?IY;p!{omN7-!6fZ(V?pvQfNeAQR1pPdpnk5zCSS z|9~;+JnnS>(eFv`UwSg>pcm_-526wDDhYk|QuFna|LWBXwBNOY((7X%zNKq;E=1T- zQ`qV-e(x*I`NYHNWWT!D+3KpW*OwQs3%tMnwk}xXxyfoY&AYye4fe%n%u$r=MhBm*dlYoWRtWp7i5aQOD~wCFR`yU2sK)7;&yry+vFU z^^f_@Jz7uG?cH`HoO78WgmXz{oanPp)J$R(Xn4BGx#^7z%=wUcF`XSgKCW_#_hB-y zl&y$zbe!45P!l6tFEOyZ%qf!lwzw4&n0E39mFSqPAL3%-t}K@EewX2ass*%twO!oc z2mp(TnO}{-V3FjkW1Rc_`GM=S@t>d8FY*Gn4yB8&U(1_aR|S@}noaT860%^>JuN(k z`w5HBqhrM#;_Twc!q%H+=Mv;CBwJ!q`83!mC(y_8e(L?VElze9a-?Bd2+^Z(O`7~X zr4%uWymnR%X3iq=q;R}Tn^k1rCzg}s%B8&zmjW76n3E0^SrVoY%`X&?W%Fpt^8$d2 z-j=AUMK~D0Aqu9%9Cfx_!(WX{&hfIGY`e&*!hX3@QQ~AFQz$0_XVQeG^c9>toP6zT z0vWKe07>fb!X=*L2Bf;Sed-4t@98{1F|AnlUyLX&n%&BIcSg2vak8WWE7ps{W;!1| z5gJsDA<;}T(u|ub(rqKfoEsneqvM!c*1#W8xjfgiCpe5@&dbavbEUnR7G-2rwY4i{ zVWe?nh=cb)B~4%zesy|+#i)o&rLwEN0SlvasU=AwH3g37XnKZJg(a?I%2K*(09^Vx zt2da5Jp*JRmQOaTGOf6=B2<#PtVO-C7|agUOey8Dy6DWCY{h1vu1iR?sCz{DI0=cH z_MN?JnarFOHAT)kn1cg5^+` z_VmlJ8R`;ZF_(^2(`{0$GGrT8St%sRZZ>g(Doe*dQ893!#5K~pwXjI#((6J`MeC$x zNj3wme$Yq`SQ{=Z(u`?tr^9Zd$)zC8Bl!_=XQHxW6W*q2YZucgTEExCIBi>%ioLfW z77`$?>H~36Do54`7a=0EvQOPwkd?5<#X&qF@99-!Pny7*kxpMDM#(32hK1X;U&=@O zTW6{hK;8(jt`A`|2xphYSv3fDc zP%2>Z8x@RhMQ=Vt6~6q144HbpK~5Bct^5Vew`QLVSJja}k=mCe)Dc%@F7i9IFHN{# z*p4|RDevVVfyxJxpVA#Zi?^XGd1& zmxdG5nTEI-i}j2j?q53qlPrnyHhUz8_bAG4>LywaTXqSNvs7BGx{fWmH(e;3UekAR zY#h0L)&OlA5BIya-Cud= zu5j4)hMKsRM74p*3o3)&hM_#HEQjW^R@DLf*#fOnFr-zXO4*`js!G|WhNhZsTti)@ zK&FPl_*@Ozo?)CmAV@i!KG}fHo)0dq7^Te=cY6V8&DUxSd|j%gEm482!*o5MRtfH6 zdRcvxR#L#E`Tm=2vcL-%GhvD zgeh9<=AcRlTwLLWSIG_dNSX$$OK$%-Z4<27r?ptT1+{un_Xd9WCB+p`Dv?X)s)Bx? zi|K&6N#?m_`>=k#uR8kkm5m`TN}V)l#cuO}Ij^N@bkCPzdbHHQ*)I^cdq6d<-XF

?@Ib4$R$ZbA6MIsF(ZEk4Ii=sfRBa)&Yo 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�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 TY(h;@!?}i$7z!3V8^-w`q)kqw literal 0 HcmV?d00001 diff --git a/DepFiles/unittest/junit-4.8.2.jar b/DepFiles/unittest/junit-4.8.2.jar deleted file mode 100644 index 5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237344 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 + - - - + + diff --git a/pom.xml b/pom.xml index 01d43877..6738b3ae 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1 + 2.0.0 jar Esri Geometry API for Java @@ -98,8 +98,7 @@ 1.6 - 20140107 - 1.9.13 + 2.6.5 4.12 @@ -110,14 +109,10 @@ - org.json - json - ${json.version} - - - org.codehaus.jackson - jackson-core-asl + com.fasterxml.jackson.core + jackson-core ${jackson.version} + false junit diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 7bed73ea..1339538c 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,9 +29,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; +import com.fasterxml.jackson.core.JsonParser; /** * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. @@ -57,10 +55,27 @@ public class GeometryEngine { * spatial reference. */ public static MapGeometry jsonToGeometry(JsonParser json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); return geom; } + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonReader json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. @@ -75,7 +90,7 @@ public static MapGeometry jsonToGeometry(JsonParser json) { * @throws IOException * @throws JsonParseException */ - public static MapGeometry jsonToGeometry(String json) throws JsonParseException, IOException { + public static MapGeometry jsonToGeometry(String json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); return geom; } @@ -126,43 +141,60 @@ public static String geometryToGeoJson(Geometry geometry) { return exporter.execute(geometry); } - /** - * Exports the specified geometry instance to its GeoJSON representation. - * - *See OperatorExportToGeoJson. - * - * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, - * Geometry geometry) - * - * @param wkid - * The spatial reference Well Known ID to be used for the GeoJSON representation. - * @param geometry - * The geometry to be exported to GeoJSON. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToGeoJson( - wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { + MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); + return geom; + } - /** - * Exports the specified geometry instance to it's JSON representation. - * - *See OperatorImportFromGeoJson. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(SpatialReference spatialReference, - Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory - .getOperator(Operator.Type.ExportToGeoJson); + /** + * Exports the specified geometry instance to its GeoJSON representation. + * + * See OperatorExportToGeoJson. + * + * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, + * Geometry geometry) + * + * @param wkid + * The spatial reference Well Known ID to be used for the GeoJSON + * representation. + * @param geometry + * The geometry to be exported to GeoJSON. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } - return exporter.execute(spatialReference, geometry); - } + /** + * Exports the specified geometry instance to it's JSON representation. + * + * See OperatorImportFromGeoJson. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(spatialReference, geometry); + } /** * Imports geometry from the ESRI shape file format. @@ -230,26 +262,6 @@ public static Geometry geometryFromWkt(String wkt, int importFlags, return op.execute(importFlags, geometryType, wkt, null); } - /** - * Imports a geometry from a geoJson string. - * - * See OperatorImportFromGeoJson. - * - * @param geoJson The string containing the geometry in geoJson format. - * @param importFlags Use the {@link GeoJsonImportFlags} interface. - * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the geoJson context. - * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the geoJson string. - */ - @Deprecated - public static MapGeometry geometryFromGeoJson(String geoJson, - int importFlags, Geometry.Type geometryType) throws JSONException { - OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory - .getOperator(Operator.Type.ImportFromGeoJson); - return op.execute(importFlags, geometryType, geoJson, null); - } - /** * Exports a geometry to a string in WKT format. * diff --git a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java deleted file mode 100644 index 1350f623..00000000 --- a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; - -final class JSONArrayEnumerator { - - private JSONArray m_jsonArray; - private boolean m_bStarted; - private int m_currentIndex; - - JSONArrayEnumerator(JSONArray jsonArray) { - m_bStarted = false; - m_currentIndex = -1; - m_jsonArray = jsonArray; - } - - Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonArray.length()) { - throw new GeometryException("invalid call"); - } - - return m_jsonArray.opt(m_currentIndex); - } - - boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_bStarted = true; - } else if (m_currentIndex != m_jsonArray.length()) { - m_currentIndex++; - } - - return m_currentIndex != m_jsonArray.length(); - } -} - diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java deleted file mode 100644 index 2b4e25b4..00000000 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -import org.json.JSONObject; - -import java.util.Iterator; - -final class JSONObjectEnumerator { - - private JSONObject m_jsonObject; - private int m_troolean; - private Iterator m_keys_iter; - private String m_current_key; - - JSONObjectEnumerator(JSONObject jsonObject) { - m_troolean = 0; - m_jsonObject = jsonObject; - } - - String getCurrentKey() { - if (m_troolean != 1) { - throw new GeometryException("invalid call"); - } - - return m_current_key; - } - - Object getCurrentObject() { - if (m_troolean != 1) { - throw new GeometryException("invalid call"); - } - - return m_jsonObject.opt(m_current_key); - } - - boolean next() { - if (m_troolean == 0) { - if (m_jsonObject.length() > 0) { - m_keys_iter = m_jsonObject.keys(); - m_troolean = 1;//started - } - else { - m_troolean = -1;//stopped - } - } - - if (m_troolean == 1) {//still exploring - if (m_keys_iter.hasNext()) { - m_current_key = (String)m_keys_iter.next(); - } - else { - m_troolean = -1; //done - } - } - - return m_troolean == 1; - } -} diff --git a/src/main/java/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java index 71feb32a..14bcf142 100644 --- a/src/main/java/com/esri/core/geometry/JSONUtils.java +++ b/src/main/java/com/esri/core/geometry/JSONUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,26 +23,21 @@ */ package com.esri.core.geometry; -import java.io.IOException; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonToken; - final class JSONUtils { static boolean isObjectStart(JsonReader parser) throws Exception { - return parser.currentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT - : parser.currentToken() == JsonToken.START_OBJECT; + return parser.currentToken() == null ? parser.nextToken() == JsonReader.Token.START_OBJECT + : parser.currentToken() == JsonReader.Token.START_OBJECT; } - static double readDouble(JsonReader parser) throws JsonParseException, - IOException, Exception { - if (parser.currentToken() == JsonToken.VALUE_NUMBER_FLOAT) + static double readDouble(JsonReader parser) { + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_FLOAT) return parser.currentDoubleValue(); - else if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + else if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) return parser.currentIntValue(); - else if (parser.currentToken() == JsonToken.VALUE_NULL) + else if (parser.currentToken() == JsonReader.Token.VALUE_NULL) return NumberUtils.NaN(); - else if (parser.currentToken() == JsonToken.VALUE_STRING) + else if (parser.currentToken() == JsonReader.Token.VALUE_STRING) if (parser.currentString().equals("NaN")) return NumberUtils.NaN(); diff --git a/src/main/java/com/esri/core/geometry/JsonGeometryException.java b/src/main/java/com/esri/core/geometry/JsonGeometryException.java index a5552901..7402e0de 100644 --- a/src/main/java/com/esri/core/geometry/JsonGeometryException.java +++ b/src/main/java/com/esri/core/geometry/JsonGeometryException.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,13 +21,15 @@ email: contracts@esri.com */ + package com.esri.core.geometry; /** * A runtime exception raised when a JSON related exception occurs. */ public class JsonGeometryException extends GeometryException { - + private static final long serialVersionUID = 1L; + /** * Constructs a Json Geometry Exception with the given error string/message. * @@ -37,4 +39,16 @@ public class JsonGeometryException extends GeometryException { public JsonGeometryException(String str) { super(str); } + + /** + * Constructs a Json Geometry Exception with the given another exception. + * + * @param ex + * - The exception to copy the message from. + */ + public JsonGeometryException(Exception ex) { + super(ex.getMessage()); + } + } + diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index bf382dd9..90427c63 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,56 +21,151 @@ email: contracts@esri.com */ -package com.esri.core.geometry; - -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; -import org.codehaus.jackson.JsonParseException; +package com.esri.core.geometry; -import java.io.IOException; +import com.fasterxml.jackson.core.*; -final class JsonParserReader extends JsonReader { +/** + * A throw in JsonReader built around the Jackson JsonParser. + * + */ +public class JsonParserReader implements JsonReader { private JsonParser m_jsonParser; - JsonParserReader(JsonParser jsonParser) { + public JsonParserReader(JsonParser jsonParser) { m_jsonParser = jsonParser; } + + /** + * Creates a JsonReader for the string. + * The nextToken is called by this method. + */ + public static JsonReader createFromString(String str) { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createParser(str); + + jsonParser.nextToken(); + return new JsonParserReader(jsonParser); + } + catch (Exception ex) { + throw new JsonGeometryException(ex.getMessage()); + } + } + + /** + * Creates a JsonReader for the string. + * The nextToken is not called by this method. + */ + public static JsonReader createFromStringNNT(String str) { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createParser(str); + + return new JsonParserReader(jsonParser); + } + catch (Exception ex) { + throw new JsonGeometryException(ex.getMessage()); + } + } + + private static Token mapToken(JsonToken token) { + if (token == JsonToken.END_ARRAY) + return Token.END_ARRAY; + if (token == JsonToken.END_OBJECT) + return Token.END_OBJECT; + if (token == JsonToken.FIELD_NAME) + return Token.FIELD_NAME; + if (token == JsonToken.START_ARRAY) + return Token.START_ARRAY; + if (token == JsonToken.START_OBJECT) + return Token.START_OBJECT; + if (token == JsonToken.VALUE_FALSE) + return Token.VALUE_FALSE; + if (token == JsonToken.VALUE_NULL) + return Token.VALUE_NULL; + if (token == JsonToken.VALUE_NUMBER_FLOAT) + return Token.VALUE_NUMBER_FLOAT; + if (token == JsonToken.VALUE_NUMBER_INT) + return Token.VALUE_NUMBER_INT; + if (token == JsonToken.VALUE_STRING) + return Token.VALUE_STRING; + if (token == JsonToken.VALUE_TRUE) + return Token.VALUE_TRUE; + if (token == null) + return null; + + throw new JsonGeometryException("unexpected token"); + } @Override - JsonToken nextToken() throws JSONException, JsonParseException, IOException { - JsonToken token = m_jsonParser.nextToken(); - return token; + public Token nextToken() throws JsonGeometryException { + try { + JsonToken token = m_jsonParser.nextToken(); + return mapToken(token); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } } @Override - JsonToken currentToken() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getCurrentToken(); + public Token currentToken() throws JsonGeometryException { + try { + return mapToken(m_jsonParser.getCurrentToken()); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } } @Override - void skipChildren() throws JSONException, JsonParseException, IOException { - m_jsonParser.skipChildren(); + public void skipChildren() throws JsonGeometryException { + try { + m_jsonParser.skipChildren(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } @Override - String currentString() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getText(); + public String currentString() throws JsonGeometryException { + try { + return m_jsonParser.getText(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } @Override - double currentDoubleValue() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getValueAsDouble(); + public double currentDoubleValue() throws JsonGeometryException { + try { + return m_jsonParser.getValueAsDouble(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } @Override - int currentIntValue() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getValueAsInt(); + public int currentIntValue() throws JsonGeometryException { + try { + return m_jsonParser.getValueAsInt(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } + + @Override + public boolean currentBooleanValue() { + Token t = currentToken(); + if (t == Token.VALUE_TRUE) + return true; + else if (t == Token.VALUE_FALSE) + return false; + throw new JsonGeometryException("Not a boolean"); } } diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index b1f69d95..541143e3 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,29 +23,38 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; -import org.codehaus.jackson.JsonParseException; - -import java.io.IOException; - -abstract class JsonReader { - - abstract JsonToken nextToken() throws JSONException, JsonParseException, IOException; - - abstract JsonToken currentToken() throws JSONException, JsonParseException, IOException; - - abstract void skipChildren() throws JSONException, JsonParseException, IOException; - - abstract String currentString() throws JSONException, JsonParseException, IOException; - - abstract double currentDoubleValue() throws JSONException, JsonParseException, IOException; - - abstract int currentIntValue() throws JSONException, JsonParseException, IOException; +/** + * An abstract reader for Json. + * + * See JsonParserReader for a concrete implementation around JsonParser. + */ +abstract public interface JsonReader { + public static enum Token { + END_ARRAY, + END_OBJECT, + FIELD_NAME, + START_ARRAY, + START_OBJECT, + VALUE_FALSE, + VALUE_NULL, + VALUE_NUMBER_FLOAT, + VALUE_NUMBER_INT, + VALUE_STRING, + VALUE_TRUE + } + + abstract public Token nextToken() throws JsonGeometryException; + + abstract public Token currentToken() throws JsonGeometryException; + + abstract public void skipChildren() throws JsonGeometryException; + + abstract public String currentString() throws JsonGeometryException; + + abstract public double currentDoubleValue() throws JsonGeometryException; + + abstract public int currentIntValue() throws JsonGeometryException; + + abstract public boolean currentBooleanValue() throws JsonGeometryException; } diff --git a/src/main/java/com/esri/core/geometry/JsonParserCursor.java b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java similarity index 68% rename from src/main/java/com/esri/core/geometry/JsonParserCursor.java rename to src/main/java/com/esri/core/geometry/JsonReaderCursor.java index 1a1f14f3..94f72a30 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,20 +21,34 @@ email: contracts@esri.com */ -package com.esri.core.geometry; +/* + COPYRIGHT 1995-2017 ESRI -import org.codehaus.jackson.JsonParser; + TRADE SECRETS: ESRI PROPRIETARY AND CONFIDENTIAL + Unpublished material - all rights reserved under the + Copyright Laws of the United States. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; /** - * An abstract JsonParser Cursor class. + * An abstract JsonReader Cursor class. */ -abstract class JsonParserCursor { +abstract class JsonReaderCursor { /** - * Moves the cursor to the next JsonParser. Returns null when reached the + * Moves the cursor to the next JsonReader. Returns null when reached the * end. */ - public abstract JsonParser next(); + public abstract JsonReader next(); /** * Returns the ID of the current geometry. The ID is propagated across the diff --git a/src/main/java/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java deleted file mode 100644 index 91028624..00000000 --- a/src/main/java/com/esri/core/geometry/JsonValueReader.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -import java.util.ArrayList; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; -import org.codehaus.jackson.JsonParseException; - -import java.io.IOException; - - -final class JsonValueReader extends JsonReader { - - private Object m_object; - private JsonToken m_currentToken; - private ArrayList m_parentStack; - private ArrayList m_objIters; - private ArrayList m_arrIters; - - JsonValueReader(Object object) { - m_object = object; - - boolean bJSONObject = (m_object instanceof JSONObject); - boolean bJSONArray = (m_object instanceof JSONArray); - - if (!bJSONObject && !bJSONArray) { - throw new IllegalArgumentException(); - } - - m_parentStack = new ArrayList(0); - m_objIters = new ArrayList(0); - m_arrIters = new ArrayList(0); - - m_parentStack.ensureCapacity(4); - m_objIters.ensureCapacity(4); - m_arrIters.ensureCapacity(4); - - if (bJSONObject) { - JSONObjectEnumerator objIter = new JSONObjectEnumerator((JSONObject) m_object); - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(objIter); - m_currentToken = JsonToken.START_OBJECT; - } else { - JSONArrayEnumerator arrIter = new JSONArrayEnumerator((JSONArray) m_object); - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(arrIter); - m_currentToken = JsonToken.START_ARRAY; - } - } - - private void setCurrentToken_(Object obj) { - if (obj instanceof String) { - m_currentToken = JsonToken.VALUE_STRING; - } else if (obj instanceof Double || obj instanceof Float) { - m_currentToken = JsonToken.VALUE_NUMBER_FLOAT; - } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Short) { - m_currentToken = JsonToken.VALUE_NUMBER_INT; - } else if (obj instanceof Boolean) { - Boolean bObj = (Boolean) obj; - boolean b = bObj.booleanValue(); - if (b) { - m_currentToken = JsonToken.VALUE_TRUE; - } else { - m_currentToken = JsonToken.VALUE_FALSE; - } - } else if (obj instanceof JSONObject) { - m_currentToken = JsonToken.START_OBJECT; - } else if (obj instanceof JSONArray) { - m_currentToken = JsonToken.START_ARRAY; - } else { - m_currentToken = JsonToken.VALUE_NULL; - } - } - - Object currentObject_() { - assert (!m_parentStack.isEmpty()); - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - JSONObjectEnumerator objIter = m_objIters.get(m_objIters.size() - 1); - return objIter.getCurrentObject(); - } - - JSONArrayEnumerator arrIter = m_arrIters.get(m_arrIters.size() - 1); - return arrIter.getCurrentObject(); - } - - @Override - JsonToken nextToken() throws JSONException, JsonParseException { - if (m_parentStack.isEmpty()) { - m_currentToken = JsonToken.NOT_AVAILABLE; - return m_currentToken; - } - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - JSONObjectEnumerator iterator = m_objIters.get(m_objIters.size() - 1); - - if (m_currentToken == JsonToken.FIELD_NAME) { - Object nextJSONValue = iterator.getCurrentObject(); - - if (nextJSONValue instanceof JSONObject) { - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); - m_currentToken = JsonToken.START_OBJECT; - } else if (nextJSONValue instanceof JSONArray) { - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); - m_currentToken = JsonToken.START_ARRAY; - } else { - setCurrentToken_(nextJSONValue); - } - } else { - if (iterator.next()) { - m_currentToken = JsonToken.FIELD_NAME; - } else { - m_objIters.remove(m_objIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_OBJECT; - } - } - } else { - assert (parentType == JsonToken.START_ARRAY); - JSONArrayEnumerator iterator = m_arrIters.get(m_arrIters.size() - 1); - if (iterator.next()) { - Object nextJSONValue = iterator.getCurrentObject(); - - if (nextJSONValue instanceof JSONObject) { - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); - m_currentToken = JsonToken.START_OBJECT; - } else if (nextJSONValue instanceof JSONArray) { - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); - m_currentToken = JsonToken.START_ARRAY; - } else { - setCurrentToken_(nextJSONValue); - } - } else { - m_arrIters.remove(m_arrIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_ARRAY; - } - } - - return m_currentToken; - } - - @Override - JsonToken currentToken() throws JSONException, JsonParseException, IOException { - return m_currentToken; - } - - @Override - void skipChildren() throws JSONException, JsonParseException, IOException { - assert (!m_parentStack.isEmpty()); - - if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { - return; - } - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - m_objIters.remove(m_objIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_OBJECT; - } else { - m_arrIters.remove(m_arrIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_ARRAY; - } - } - - @Override - String currentString() throws JSONException, JsonParseException, IOException { - if (m_currentToken == JsonToken.FIELD_NAME) { - return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); - } - - if (m_currentToken != JsonToken.VALUE_STRING) { - throw new GeometryException("invalid call"); - } - - return ((String) currentObject_()).toString(); - } - - @Override - double currentDoubleValue() throws JSONException, JsonParseException, IOException { - if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).doubleValue(); - } - - @Override - int currentIntValue() throws JSONException, JsonParseException, IOException { - if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).intValue(); - } -} diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index f6d606bb..dce85615 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -25,7 +25,7 @@ package com.esri.core.geometry; -import java.util.ArrayList; +import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; final class MultiPathImpl extends MultiVertexGeometryImpl { @@ -2561,30 +2561,34 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { return true; } - boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { - if (m_accelerators == null) { - m_accelerators = new GeometryAccelerators(); - } + boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } - //TODO: when less than two envelopes - no need to this. + // TODO: when less than two envelopes - no need to this. - if (m_accelerators.getQuadTreeForPaths() != null) - return true; + if (m_accelerators.getQuadTreeForPaths() != null) + return true; - m_accelerators._setQuadTreeForPaths(null); - QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); - m_accelerators._setQuadTreeForPaths(quad_tree_impl); + m_accelerators._setQuadTreeForPaths(null); + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); + m_accelerators._setQuadTreeForPaths(quad_tree_impl); - return true; - } + return true; + } - void setFillRule(int rule) { - assert(m_bPolygon); - m_fill_rule = rule; - } - int getFillRule() { - return m_fill_rule; - } + void setFillRule(int rule) { + assert (m_bPolygon); + m_fill_rule = rule; + } + int getFillRule() { + return m_fill_rule; + } + void clearDirtyOGCFlags() { + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); + } } + diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index ffceb73f..9dcd804d 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ public enum Type { Project, ExportToJson, ImportFromJson, - @Deprecated ImportMapGeometryFromJson, ExportToESRIShape, ImportFromESRIShape, Union, Difference, diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 160bcffd..727b4a69 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -38,10 +38,6 @@ import java.nio.channels.FileChannel; import java.util.HashMap; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; - /** *An abstract class that represent the basic OperatorFactory interface. */ @@ -58,8 +54,6 @@ public class OperatorFactoryLocal extends OperatorFactory { new OperatorExportToJsonLocal()); st_supportedOperators.put(Type.ImportFromJson, new OperatorImportFromJsonLocal()); - st_supportedOperators.put(Type.ImportMapGeometryFromJson, - new OperatorImportFromJsonLocal()); st_supportedOperators.put(Type.ExportToESRIShape, new OperatorExportToESRIShapeLocal()); st_supportedOperators.put(Type.ImportFromESRIShape, @@ -202,14 +196,7 @@ public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { } catch (Exception ex) { } - JsonFactory jf = new JsonFactory(); - JsonParser jp = null; - try { - jp = jf.createJsonParser(jsonString); - jp.nextToken(); - } catch (Exception ex) { - } - MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jp); + MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); return mapGeom; } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 88cb4653..87de2d4a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,11 +23,6 @@ */ package com.esri.core.geometry; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - public abstract class OperatorImportFromGeoJson extends Operator { @Override @@ -43,7 +38,7 @@ public Type getType() { * @return Returns the imported MapGeometry. * @throws JsonGeometryException */ - public abstract MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, ProgressTracker progressTracker) throws JSONException; + public abstract MapGeometry execute(int importFlags, Geometry.Type type, JsonReader jsonReader, ProgressTracker progressTracker); /** * Deprecated, use version without import_flags. @@ -57,7 +52,7 @@ public Type getType() { * @throws JSONException * */ - public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; + public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker); /** * @@ -68,7 +63,7 @@ public Type getType() { * @return Returns the imported MapOGCStructure. * @throws JSONException */ - public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; + public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker); public static OperatorImportFromGeoJson local() { return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromGeoJson); diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index e04952a4..78e9c8b1 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -24,57 +24,48 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; import java.util.ArrayList; class OperatorImportFromGeoJsonLocal extends OperatorImportFromGeoJson { + static enum GeoJsonType { + Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection; + static GeoJsonType fromGeoJsonValue(int v) { + return GeoJsonType.values()[v - 1]; + } + + public int geogsjonvalue() { + return ordinal() + 1; + } + }; + + static interface GeoJsonValues { + public final static int Point = GeoJsonType.Point.geogsjonvalue(); + public final static int LineString = GeoJsonType.LineString.geogsjonvalue(); + public final static int Polygon = GeoJsonType.Polygon.geogsjonvalue(); + public final static int MultiPoint = GeoJsonType.MultiPoint.geogsjonvalue(); + public final static int MultiLineString = GeoJsonType.MultiLineString.geogsjonvalue(); + public final static int MultiPolygon = GeoJsonType.MultiPolygon.geogsjonvalue(); + public final static int GeometryCollection = GeoJsonType.GeometryCollection.geogsjonvalue(); + }; @Override - public MapGeometry execute(int importFlags, Geometry.Type type, String geoJsonString, - ProgressTracker progressTracker) throws JSONException { - try { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParser = factory.createJsonParser(geoJsonString); - - jsonParser.nextToken(); - - MapGeometry map_geometry = OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, - new JsonParserReader(jsonParser), progressTracker, false); - return map_geometry; - - } catch (JSONException jsonException) { - throw jsonException; - } catch (JsonParseException jsonParseException) { - throw new JSONException(jsonParseException.getMessage()); - } catch (IOException ioException) { - throw new JSONException(ioException.getMessage()); - } + public MapGeometry execute(int importFlags, Geometry.Type type, + String geoJsonString, ProgressTracker progressTracker) + throws JsonGeometryException { + MapGeometry map_geometry = OperatorImportFromGeoJsonHelper + .importFromGeoJson(importFlags, type, JsonParserReader.createFromString(geoJsonString), progressTracker, false); + return map_geometry; } @Override - public MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, - ProgressTracker progressTracker) throws JSONException { - if (jsonObject == null) + public MapGeometry execute(int importFlags, Geometry.Type type, + JsonReader jsonReader, ProgressTracker progressTracker) + throws JsonGeometryException { + if (jsonReader == null) return null; - try { - return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, new JsonValueReader(jsonObject), - progressTracker, false); - } catch (JSONException jsonException) { - throw jsonException; - } catch (JsonParseException jsonParseException) { - throw new JSONException(jsonParseException.getMessage()); - } catch (IOException ioException) { - throw new JSONException(ioException.getMessage()); - } + return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, + type, jsonReader, progressTracker, false); } static final class OperatorImportFromGeoJsonHelper { @@ -91,6 +82,8 @@ static final class OperatorImportFromGeoJsonHelper { private boolean m_b_has_ms_known; private int m_num_embeddings; + int m_ogcType; + OperatorImportFromGeoJsonHelper() { m_position = null; m_zs = null; @@ -103,45 +96,129 @@ static final class OperatorImportFromGeoJsonHelper { m_b_has_zs_known = false; m_b_has_ms_known = false; m_num_embeddings = 0; + m_ogcType = 0; } - static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonReader json_iterator, + static MapGeometry importFromGeoJson(int importFlags, + Geometry.Type type, JsonReader json_iterator, ProgressTracker progress_tracker, boolean skip_coordinates) - throws JSONException, JsonParseException, IOException { - assert(json_iterator.currentToken() == JsonToken.START_OBJECT); + throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( + importFlags, type, json_iterator, progress_tracker, + skip_coordinates, 0); + + if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) + throw new JsonGeometryException("parsing error"); + + return new MapGeometry(ms.m_ogcStructure.m_geometry, + ms.m_spatialReference); + } + static MapOGCStructure importFromGeoJson(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates, + int recursion) throws JsonGeometryException { OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( + importFlags, type, json_iterator, progress_tracker, + skip_coordinates, recursion); + + if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) + throw new JsonGeometryException("parsing error"); + + return ms; + } + MapOGCStructure importFromGeoJsonImpl(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates, + int recursion) throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = this; boolean b_type_found = false; boolean b_coordinates_found = false; boolean b_crs_found = false; boolean b_crsURN_found = false; - String geo_json_type = null; + boolean b_geometry_collection = false; + boolean b_geometries_found = false; + GeoJsonType geo_json_type = null; Geometry geometry = null; SpatialReference spatial_reference = null; - JsonToken current_token; + JsonReader.Token current_token; String field_name = null; + MapOGCStructure ms = new MapOGCStructure(); - while ((current_token = json_iterator.nextToken()) != JsonToken.END_OBJECT) { + while ((current_token = json_iterator.nextToken()) != JsonReader.Token.END_OBJECT) { field_name = json_iterator.currentString(); if (field_name.equals("type")) { if (b_type_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_type_found = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } - geo_json_type = json_iterator.currentString(); + String s = json_iterator.currentString(); + try { + geo_json_type = GeoJsonType.valueOf(s); + } catch (Exception ex) { + throw new JsonGeometryException(s); + } + + if (geo_json_type == GeoJsonType.GeometryCollection) { + if (type != Geometry.Type.Unknown) + throw new JsonGeometryException("parsing error"); + + b_geometry_collection = true; + } + } else if (field_name.equals("geometries")) { + b_geometries_found = true; + if (type != Geometry.Type.Unknown) + throw new JsonGeometryException("parsing error"); + + if (recursion > 10) { + throw new JsonGeometryException("deep geojson"); + } + + if (skip_coordinates) { + json_iterator.skipChildren(); + } else { + current_token = json_iterator.nextToken(); + + ms.m_ogcStructure = new OGCStructure(); + ms.m_ogcStructure.m_type = GeoJsonValues.GeometryCollection; + ms.m_ogcStructure.m_structures = new ArrayList( + 0); + + if (current_token == JsonReader.Token.START_ARRAY) { + current_token = json_iterator.nextToken(); + while (current_token != JsonReader.Token.END_ARRAY) { + MapOGCStructure child = importFromGeoJson( + importFlags + | GeoJsonImportFlags.geoJsonImportSkipCRS, + type, json_iterator, + progress_tracker, false, + recursion + 1); + ms.m_ogcStructure.m_structures + .add(child.m_ogcStructure); + + current_token = json_iterator.nextToken(); + } + } + else if (current_token != JsonReader.Token.VALUE_NULL) { + throw new JsonGeometryException("parsing error"); + } + } } else if (field_name.equals("coordinates")) { + if (b_coordinates_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_coordinates_found = true; @@ -152,36 +229,38 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe } else {// According to the spec, the value of the // coordinates must be an array. However, I do an // extra check for null too. - if (current_token != JsonToken.VALUE_NULL) { - if (current_token != JsonToken.START_ARRAY) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_NULL) { + if (current_token != JsonReader.Token.START_ARRAY) { + throw new JsonGeometryException("parsing error"); } - geo_json_helper.import_coordinates_(json_iterator, progress_tracker); + geo_json_helper.import_coordinates_(json_iterator, + progress_tracker); } } } else if (field_name.equals("crs")) { if (b_crs_found || b_crsURN_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_crs_found = true; current_token = json_iterator.nextToken(); if ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) - spatial_reference = importSpatialReferenceFromCrs(json_iterator, progress_tracker); + spatial_reference = importSpatialReferenceFromCrs( + json_iterator, progress_tracker); else json_iterator.skipChildren(); } else if (field_name.equals("crsURN")) { if (b_crs_found || b_crsURN_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_crsURN_found = true; current_token = json_iterator.nextToken(); - spatial_reference = importSpatialReferenceFromCrsUrn_(json_iterator, - progress_tracker); + spatial_reference = importSpatialReferenceFromCrsUrn_( + json_iterator, progress_tracker); } else { json_iterator.nextToken(); json_iterator.skipChildren(); @@ -190,14 +269,27 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe // According to the spec, a GeoJSON object must have both a type and // a coordinates array - if (!b_type_found || (!b_coordinates_found && !skip_coordinates)) { - throw new IllegalArgumentException("invalid argument"); + if (!b_type_found || (!b_geometry_collection && !b_coordinates_found && !skip_coordinates)) { + throw new JsonGeometryException("parsing error"); + } + + if ((!b_geometry_collection && b_geometries_found) || (b_geometry_collection && !b_geometries_found)) { + throw new JsonGeometryException("parsing error");//found "geometries" but did not see "GeometryCollection" } + - if (!skip_coordinates) - geometry = geo_json_helper.createGeometry_(geo_json_type, type.value()); + if (!skip_coordinates && !b_geometry_collection) { + geometry = geo_json_helper.createGeometry_(geo_json_type, + type.value()); + + ms.m_ogcStructure = new OGCStructure(); + ms.m_ogcStructure.m_type = m_ogcType; + ms.m_ogcStructure.m_geometry = geometry; + } - if (!b_crs_found && !b_crsURN_found && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + if (!b_crs_found + && !b_crsURN_found + && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) && ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0)) { spatial_reference = SpatialReference.create(4326); // the spec // gives a @@ -207,12 +299,8 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe // is given } - MapGeometry map_geometry = new MapGeometry(geometry, spatial_reference); - - assert(geo_json_helper.m_paths == null || (geo_json_helper.m_path_flags != null - && geo_json_helper.m_paths.size() == geo_json_helper.m_path_flags.size())); - - return map_geometry; + ms.m_spatialReference = spatial_reference; + return ms; } // We have to import the coordinates in the most general way possible to @@ -225,86 +313,88 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe // coordinates // into the attribute stream(s), and will later assign them to a // geometry after the type tag is found. - private void import_coordinates_(JsonReader json_iterator, ProgressTracker progress_tracker) - throws JSONException, JsonParseException, IOException { - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + private void import_coordinates_(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JsonGeometryException { + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); int coordinates_level_lower = 1; int coordinates_level_upper = 4; json_iterator.nextToken(); - while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (isDouble_(json_iterator)) { if (coordinates_level_upper > 1) { coordinates_level_upper = 1; } - } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { if (coordinates_level_lower < 2) { coordinates_level_lower = 2; } } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower > coordinates_level_upper) { throw new IllegalArgumentException("invalid argument"); } - if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 1) {// special - // code - // for - // Points + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 1) {// special + // code + // for + // Points readCoordinateAsPoint_(json_iterator); } else { boolean b_add_path_level_3 = true; boolean b_polygon_start_level_4 = true; - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); json_iterator.nextToken(); - while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (isDouble_(json_iterator)) { if (coordinates_level_upper > 2) { coordinates_level_upper = 2; } - } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { if (coordinates_level_lower < 3) { coordinates_level_lower = 3; } } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower > coordinates_level_upper) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } - if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 2) {// LineString - // or - // MultiPoint + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 2) {// LineString + // or + // MultiPoint addCoordinate_(json_iterator); } else { boolean b_add_path_level_4 = true; - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); json_iterator.nextToken(); - while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (isDouble_(json_iterator)) { if (coordinates_level_upper > 3) { coordinates_level_upper = 3; } - } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { if (coordinates_level_lower < 4) { coordinates_level_lower = 4; } } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower > coordinates_level_upper) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower == coordinates_level_upper @@ -318,16 +408,15 @@ private void import_coordinates_(JsonReader json_iterator, ProgressTracker progr addCoordinate_(json_iterator); } else { - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); json_iterator.nextToken(); - if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (!isDouble_(json_iterator)) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } - assert(coordinates_level_lower == coordinates_level_upper - && coordinates_level_lower == 4); + assert (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 4); // MultiPolygon if (b_add_path_level_4) { @@ -363,8 +452,8 @@ private void import_coordinates_(JsonReader json_iterator, ProgressTracker progr } private void readCoordinateAsPoint_(JsonReader json_iterator) - throws JSONException, JsonParseException, IOException { - assert(isDouble_(json_iterator)); + throws JsonGeometryException { + assert (isDouble_(json_iterator)); m_point = new Point(); @@ -391,16 +480,18 @@ private void readCoordinateAsPoint_(JsonReader json_iterator) m_point.setM(m); } - if (json_iterator.currentToken() != JsonToken.END_ARRAY) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + throw new JsonGeometryException("parsing error"); } } - private void addCoordinate_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { - assert(isDouble_(json_iterator)); + private void addCoordinate_(JsonReader json_iterator) + throws JsonGeometryException { + assert (isDouble_(json_iterator)); if (m_position == null) { - m_position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + m_position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); } double x = readDouble_(json_iterator); @@ -417,11 +508,14 @@ private void addCoordinate_(JsonReader json_iterator) throws JSONException, Json if (!m_b_has_zs_known) { m_b_has_zs_known = true; m_b_has_zs = true; - m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + m_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); } else { if (!m_b_has_zs) { - m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, - VertexDescription.getDefaultValue(Semantics.Z)); + m_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(size >> 1, + VertexDescription + .getDefaultValue(Semantics.Z)); m_b_has_zs = true; } } @@ -444,11 +538,14 @@ private void addCoordinate_(JsonReader json_iterator) throws JSONException, Json if (!m_b_has_ms_known) { m_b_has_ms_known = true; m_b_has_ms = true; - m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + m_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); } else { if (!m_b_has_ms) { - m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, - VertexDescription.getDefaultValue(Semantics.M)); + m_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(size >> 1, + VertexDescription + .getDefaultValue(Semantics.M)); m_b_has_ms = true; } } @@ -467,14 +564,15 @@ private void addCoordinate_(JsonReader json_iterator) throws JSONException, Json } } - if (json_iterator.currentToken() != JsonToken.END_ARRAY) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + throw new JsonGeometryException("parsing error"); } } private void addPath_() { if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); } if (m_position == null) { @@ -486,68 +584,77 @@ private void addPath_() { private void addPathFlag_(boolean b_polygon_start) { if (m_path_flags == null) { - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(0); + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(0); } if (b_polygon_start) { - m_path_flags.add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + m_path_flags + .add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); } else { m_path_flags.add((byte) PathFlags.enumClosed); } } - private double readDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { - JsonToken current_token = json_iterator.currentToken(); - if (current_token == JsonToken.VALUE_NULL - || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + private double readDouble_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.currentToken(); + if (current_token == JsonReader.Token.VALUE_NULL + || (current_token == JsonReader.Token.VALUE_STRING && json_iterator + .currentString().equals("NaN"))) { return NumberUtils.NaN(); } else { return json_iterator.currentDoubleValue(); } } - private boolean isDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { - JsonToken current_token = json_iterator.currentToken(); + private boolean isDouble_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.currentToken(); - if (current_token == JsonToken.VALUE_NUMBER_FLOAT) { + if (current_token == JsonReader.Token.VALUE_NUMBER_FLOAT) { return true; } - if (current_token == JsonToken.VALUE_NUMBER_INT) { + if (current_token == JsonReader.Token.VALUE_NUMBER_INT) { return true; } - if (current_token == JsonToken.VALUE_NULL - || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + if (current_token == JsonReader.Token.VALUE_NULL + || (current_token == JsonReader.Token.VALUE_STRING && json_iterator + .currentString().equals("NaN"))) { return true; } return false; } - private Geometry createGeometry_(String geo_json_type, int type) - throws JSONException, JsonParseException, IOException { + //does not accept GeometryCollection + private Geometry createGeometry_(GeoJsonType geo_json_type, int type) + throws JsonGeometryException { Geometry geometry; if (type != Geometry.GeometryType.Unknown) { switch (type) { case Geometry.GeometryType.Polygon: - if (!geo_json_type.equals("MultiPolygon") && !geo_json_type.equals("Polygon")) { + if (geo_json_type != GeoJsonType.MultiPolygon + && geo_json_type != GeoJsonType.Polygon) { throw new GeometryException("invalid shape type"); } break; case Geometry.GeometryType.Polyline: - if (!geo_json_type.equals("MultiLineString") && !geo_json_type.equals("LineString")) { + if (geo_json_type != GeoJsonType.MultiLineString + && geo_json_type != GeoJsonType.LineString) { throw new GeometryException("invalid shape type"); } break; case Geometry.GeometryType.MultiPoint: - if (!geo_json_type.equals("MultiPoint")) { + if (geo_json_type != GeoJsonType.MultiPoint) { throw new GeometryException("invalid shape type"); } break; case Geometry.GeometryType.Point: - if (!geo_json_type.equals("Point")) { + if (geo_json_type != GeoJsonType.Point) { throw new GeometryException("invalid shape type"); } break; @@ -555,70 +662,88 @@ private Geometry createGeometry_(String geo_json_type, int type) throw new GeometryException("invalid shape type"); } } - + + m_ogcType = geo_json_type.geogsjonvalue(); + if (geo_json_type == GeoJsonType.GeometryCollection) + throw new IllegalArgumentException("invalid argument"); + if (m_position == null && m_point == null) { - if (geo_json_type.equals("Point")) { + switch (geo_json_type) + { + case Point: { if (m_num_embeddings > 1) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Point(); - } else if (geo_json_type.equals("MultiPoint")) { + break; + } + case MultiPoint: { if (m_num_embeddings > 2) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new MultiPoint(); - } else if (geo_json_type.equals("LineString")) { + break; + } + case LineString: { if (m_num_embeddings > 2) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Polyline(); - } else if (geo_json_type.equals("MultiLineString")) { + break; + } + case MultiLineString: { if (m_num_embeddings > 3) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Polyline(); - } else if (geo_json_type.equals("Polygon")) { + break; + } + case Polygon: { if (m_num_embeddings > 3) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Polygon(); - } else if (geo_json_type.equals("MultiPolygon")) { - assert(m_num_embeddings <= 4); + break; + } + case MultiPolygon: { + assert (m_num_embeddings <= 4); geometry = new Polygon(); - } else { - throw new IllegalArgumentException("invalid argument"); + break; + } + default: + throw new JsonGeometryException("parsing error"); } } else if (m_num_embeddings == 1) { - if (!geo_json_type.equals("Point")) { - throw new IllegalArgumentException("invalid argument"); + if (geo_json_type != GeoJsonType.Point) { + throw new JsonGeometryException("parsing error"); } - assert(m_point != null); + assert (m_point != null); geometry = m_point; } else if (m_num_embeddings == 2) { - if (geo_json_type.equals("MultiPoint")) { + if (geo_json_type == GeoJsonType.MultiPoint) { geometry = createMultiPointFromStreams_(); - } else if (geo_json_type.equals("LineString")) { + } else if (geo_json_type == GeoJsonType.LineString) { geometry = createPolylineFromStreams_(); } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } } else if (m_num_embeddings == 3) { - if (geo_json_type.equals("Polygon")) { + if (geo_json_type == GeoJsonType.Polygon) { geometry = createPolygonFromStreams_(); - } else if (geo_json_type.equals("MultiLineString")) { + } else if (geo_json_type == GeoJsonType.MultiLineString) { geometry = createPolylineFromStreams_(); } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } } else { - if (!geo_json_type.equals("MultiPolygon")) { - throw new IllegalArgumentException("invalid argument"); + if (geo_json_type != GeoJsonType.MultiPolygon) { + throw new JsonGeometryException("parsing error"); } geometry = createPolygonFromStreams_(); @@ -628,29 +753,34 @@ private Geometry createGeometry_(String geo_json_type, int type) } private Geometry createPolygonFromStreams_() { - assert(m_position != null); - assert(m_paths != null); - assert((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); + assert (m_position != null); + assert (m_paths != null); + assert ((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); Polygon polygon = new Polygon(); MultiPathImpl multi_path_impl = (MultiPathImpl) polygon._getImpl(); checkPathPointCountsForMultiPath_(true); - multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); if (m_b_has_zs) { - assert(m_zs != null); + assert (m_zs != null); multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); } if (m_b_has_ms) { - assert(m_ms != null); + assert (m_ms != null); multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); } if (m_path_flags == null) { - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); - m_path_flags.setBits(0, (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_paths.size(), (byte) 0); + m_path_flags + .setBits( + 0, + (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); for (int i = 1; i < m_path_flags.size() - 1; i++) { m_path_flags.setBits(i, (byte) PathFlags.enumClosed); @@ -659,13 +789,15 @@ private Geometry createPolygonFromStreams_() { multi_path_impl.setPathStreamRef(m_paths); multi_path_impl.setPathFlagsStreamRef(m_path_flags); - multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + multi_path_impl + .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(m_path_flags); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + m_path_flags); for (int i = 0; i < path_flags_clone.size() - 1; i++) { - assert((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); - assert((m_path_flags.read(i) & PathFlags.enumClosed) != 0); + assert ((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); + assert ((m_path_flags.read(i) & PathFlags.enumClosed) != 0); if ((path_flags_clone.read(i) & PathFlags.enumOGCStartPolygon) != 0) {// Should // be @@ -680,69 +812,76 @@ private Geometry createPolygonFromStreams_() { } } } + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); - multi_path_impl.setDirtyOGCFlags(false); + multi_path_impl.clearDirtyOGCFlags(); return polygon; } private Geometry createPolylineFromStreams_() { - assert(m_position != null); - assert((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); - assert(m_path_flags == null); + assert (m_position != null); + assert ((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); + assert (m_path_flags == null); Polyline polyline = new Polyline(); MultiPathImpl multi_path_impl = (MultiPathImpl) polyline._getImpl(); if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); m_paths.add(0); m_paths.add(m_position.size() / 2); } checkPathPointCountsForMultiPath_(false); - multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); if (m_b_has_zs) { - assert(m_zs != null); + assert (m_zs != null); multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); } if (m_b_has_ms) { - assert(m_ms != null); + assert (m_ms != null); multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); } - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_paths.size(), (byte) 0); multi_path_impl.setPathStreamRef(m_paths); multi_path_impl.setPathFlagsStreamRef(m_path_flags); - multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + multi_path_impl + .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); return polyline; } private Geometry createMultiPointFromStreams_() { - assert(m_position != null); - assert(m_paths == null); - assert(m_path_flags == null); + assert (m_position != null); + assert (m_paths == null); + assert (m_path_flags == null); MultiPoint multi_point = new MultiPoint(); - MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point._getImpl(); - multi_point_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point + ._getImpl(); + multi_point_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); if (m_b_has_zs) { - assert(m_zs != null); + assert (m_zs != null); multi_point_impl.setAttributeStreamRef(Semantics.Z, m_zs); } if (m_b_has_ms) { - assert(m_ms != null); + assert (m_ms != null); multi_point_impl.setAttributeStreamRef(Semantics.M, m_ms); } + multi_point_impl.resize(m_position.size() / 2); multi_point_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - return multi_point; } @@ -794,14 +933,15 @@ private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { int path_start = m_paths.read(path); int path_end = m_paths.read(path + 1); int path_size = path_end - path_start; - assert(path_size != 0); // we should not have added empty parts - // on import + assert (path_size != 0); // we should not have added empty parts + // on import if (path_size == 1) { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start, path_start, path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start + 1, path_start, path_size); - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start + 1, - path_start, path_size); adjusted_start += 2; } else if (path_size >= 3 && b_is_polygon) { m_position.read(path_start * 2, pt1); @@ -817,19 +957,22 @@ private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { m2 = m_ms.readAsDbl(path_end - 1); } - if (pt1.equals(pt2) && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) + if (pt1.equals(pt2) + && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) && (NumberUtils.isNaN(m1) && NumberUtils.isNaN(m2) || m1 == m2)) { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + insertIntoAdjustedStreams_(adjusted_position, + adjusted_zs, adjusted_ms, adjusted_start, path_start, path_size - 1); adjusted_start += path_size - 1; } else { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + insertIntoAdjustedStreams_(adjusted_position, + adjusted_zs, adjusted_ms, adjusted_start, path_start, path_size); adjusted_start += path_size; } } else { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, - path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start, path_start, path_size); adjusted_start += path_size; } adjusted_paths.write(path + 1, adjusted_start); @@ -841,30 +984,35 @@ private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { m_ms = adjusted_ms; } - private void insertIntoAdjustedStreams_(AttributeStreamOfDbl adjusted_position, - AttributeStreamOfDbl adjusted_zs, AttributeStreamOfDbl adjusted_ms, int adjusted_start, int path_start, - int count) { - adjusted_position.insertRange(adjusted_start * 2, m_position, path_start * 2, count * 2, true, 2, - adjusted_start * 2); + private void insertIntoAdjustedStreams_( + AttributeStreamOfDbl adjusted_position, + AttributeStreamOfDbl adjusted_zs, + AttributeStreamOfDbl adjusted_ms, int adjusted_start, + int path_start, int count) { + adjusted_position.insertRange(adjusted_start * 2, m_position, + path_start * 2, count * 2, true, 2, adjusted_start * 2); if (m_b_has_zs) { - adjusted_zs.insertRange(adjusted_start, m_zs, path_start, count, true, 1, adjusted_start); + adjusted_zs.insertRange(adjusted_start, m_zs, path_start, + count, true, 1, adjusted_start); } if (m_b_has_ms) { - adjusted_ms.insertRange(adjusted_start, m_ms, path_start, count, true, 1, adjusted_start); + adjusted_ms.insertRange(adjusted_start, m_ms, path_start, + count, true, 1, adjusted_start); } } - static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, - ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + static SpatialReference importSpatialReferenceFromCrs( + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { // According to the spec, a null crs corresponds to no spatial // reference - if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { return null; } - if (json_iterator.currentToken() == JsonToken.VALUE_STRING) {// see + if (json_iterator.currentToken() == JsonReader.Token.VALUE_STRING) {// see // http://wiki.geojson.org/RFC-001 // (this // is @@ -879,7 +1027,8 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // format) String crs_short_form = json_iterator.currentString(); - int wkid = GeoJsonCrsTables.getWkidFromCrsShortForm(crs_short_form); + int wkid = GeoJsonCrsTables + .getWkidFromCrsShortForm(crs_short_form); if (wkid == -1) { throw new GeometryException("not implemented"); @@ -895,8 +1044,8 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, return spatial_reference; } - if (json_iterator.currentToken() != JsonToken.START_OBJECT) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.START_OBJECT) { + throw new JsonGeometryException("parsing error"); } // This is to support all cases of crs identifiers I've seen. Some @@ -910,86 +1059,92 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, boolean b_found_properties_url = false; boolean b_found_properties_code = false; boolean b_found_esriwkt = false; - String crs_field = null, properties_field = null, type = null, crs_identifier_name = null, - crs_identifier_urn = null, crs_identifier_href = null, crs_identifier_url = null, esriwkt = null; + String crs_field = null; + String properties_field = null; + String crs_identifier_name = null; + String crs_identifier_urn = null; + String crs_identifier_href = null; + String crs_identifier_url = null; + String esriwkt = null; int crs_identifier_code = -1; - JsonToken current_token; + JsonReader.Token current_token; - while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { crs_field = json_iterator.currentString(); if (crs_field.equals("type")) { if (b_found_type) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_type = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } - type = json_iterator.currentString(); + //type = json_iterator.currentString(); } else if (crs_field.equals("properties")) { if (b_found_properties) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.START_OBJECT) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.START_OBJECT) { + throw new JsonGeometryException("parsing error"); } - while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { properties_field = json_iterator.currentString(); if (properties_field.equals("name")) { if (b_found_properties_name) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_name = true; crs_identifier_name = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("href")) { if (b_found_properties_href) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_href = true; crs_identifier_href = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("urn")) { if (b_found_properties_urn) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_urn = true; crs_identifier_urn = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("url")) { if (b_found_properties_url) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_url = true; crs_identifier_url = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("code")) { if (b_found_properties_code) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_code = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_NUMBER_INT) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_NUMBER_INT) { + throw new JsonGeometryException("parsing error"); } - crs_identifier_code = json_iterator.currentIntValue(); + crs_identifier_code = json_iterator + .currentIntValue(); } else { json_iterator.nextToken(); json_iterator.skipChildren(); @@ -997,15 +1152,15 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, } } else if (crs_field.equals("esriwkt")) { if (b_found_esriwkt) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_esriwkt = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } esriwkt = json_iterator.currentString(); @@ -1016,7 +1171,7 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, } if ((!b_found_type || !b_found_properties) && !b_found_esriwkt) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } int wkid = -1; @@ -1032,9 +1187,10 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // (somewhat // common) } else if (b_found_properties_urn) { - wkid = GeoJsonCrsTables.getWkidFromCrsOgcUrn(crs_identifier_urn); // see - // http://wiki.geojson.org/GeoJSON_draft_version_5 - // (rare) + wkid = GeoJsonCrsTables + .getWkidFromCrsOgcUrn(crs_identifier_urn); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) } else if (b_found_properties_url) { wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_url); // see // http://wiki.geojson.org/GeoJSON_draft_version_5 @@ -1044,11 +1200,11 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // http://wiki.geojson.org/GeoJSON_draft_version_5 // (rare) } else if (!b_found_esriwkt) { - throw new GeometryException("not implemented"); + throw new JsonGeometryException("parsing error"); } if (wkid < 0 && !b_found_esriwkt && !b_found_properties_name) { - throw new GeometryException("not implemented"); + throw new JsonGeometryException("parsing error"); } SpatialReference spatial_reference = null; @@ -1072,8 +1228,10 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // the properties // name is like // "ESRI:" - String potential_wkt = GeoJsonCrsTables.getWktFromCrsName(crs_identifier_name); - spatial_reference = SpatialReference.create(potential_wkt); + String potential_wkt = GeoJsonCrsTables + .getWktFromCrsName(crs_identifier_name); + spatial_reference = SpatialReference + .create(potential_wkt); } } catch (Exception e) { } @@ -1083,16 +1241,17 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, } // see http://geojsonwg.github.io/draft-geojson/draft.html - static SpatialReference importSpatialReferenceFromCrsUrn_(JsonReader json_iterator, - ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + static SpatialReference importSpatialReferenceFromCrsUrn_( + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { // According to the spec, a null crs corresponds to no spatial // reference - if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { return null; } - if (json_iterator.currentToken() != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } String crs_identifier_urn = json_iterator.currentString(); @@ -1121,11 +1280,11 @@ static SpatialReference importSpatialReferenceFromCrsUrn_(JsonReader json_iterat } private static String getCrsIdentifier_(JsonReader json_iterator) - throws JSONException, JsonParseException, IOException { - JsonToken current_token = json_iterator.nextToken(); + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } return json_iterator.currentString(); @@ -1133,466 +1292,28 @@ private static String getCrsIdentifier_(JsonReader json_iterator) } - static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { - if (obj.get(name) == JSONObject.NULL) - return new JSONArray(); - else - return obj.getJSONArray(name); - } - @Override - public MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) - throws JSONException { - MapOGCStructure mapOGCStructure = null; - try { - JSONObject geoJsonObject = new JSONObject(geoJsonString); - ArrayList structureStack = new ArrayList(0); - ArrayList objectStack = new ArrayList(0); - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); - OGCStructure root = new OGCStructure(); - root.m_structures = new ArrayList(0); - structureStack.add(root); // add dummy root - objectStack.add(geoJsonObject); - indices.add(0); - numGeometries.add(1); - while (!objectStack.isEmpty()) { - if (indices.getLast() == numGeometries.getLast()) { - structureStack.remove(structureStack.size() - 1); - indices.removeLast(); - numGeometries.removeLast(); - continue; - } - OGCStructure lastStructure = structureStack.get(structureStack.size() - 1); - JSONObject lastObject = objectStack.get(objectStack.size() - 1); - objectStack.remove(objectStack.size() - 1); - indices.write(indices.size() - 1, indices.getLast() + 1); - String typeString = lastObject.getString("type"); - if (typeString.equalsIgnoreCase("GeometryCollection")) { - OGCStructure next = new OGCStructure(); - next.m_type = 7; - next.m_structures = new ArrayList(0); - lastStructure.m_structures.add(next); - structureStack.add(next); - JSONArray geometries = getJSONArray(lastObject, "geometries"); - indices.add(0); - numGeometries.add(geometries.length()); - for (int i = geometries.length() - 1; i >= 0; i--) - objectStack.add(geometries.getJSONObject(i)); - } else { - int ogcType; - if (typeString.equalsIgnoreCase("Point")) - ogcType = 1; - else if (typeString.equalsIgnoreCase("LineString")) - ogcType = 2; - else if (typeString.equalsIgnoreCase("Polygon")) - ogcType = 3; - else if (typeString.equalsIgnoreCase("MultiPoint")) - ogcType = 4; - else if (typeString.equalsIgnoreCase("MultiLineString")) - ogcType = 5; - else if (typeString.equalsIgnoreCase("MultiPolygon")) - ogcType = 6; - else - throw new UnsupportedOperationException(); - - MapGeometry map_geometry = execute(import_flags | GeoJsonImportFlags.geoJsonImportSkipCRS, - Geometry.Type.Unknown, lastObject, null); - OGCStructure leaf = new OGCStructure(); - leaf.m_type = ogcType; - leaf.m_geometry = map_geometry.getGeometry(); - lastStructure.m_structures.add(leaf); - } - } - mapOGCStructure = new MapOGCStructure(); - mapOGCStructure.m_ogcStructure = root; - - if ((import_flags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) - mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(import_flags, geoJsonObject); - } catch (JSONException jsonException) { - throw jsonException; - } catch (JsonParseException jsonParseException) { - throw new JSONException(jsonParseException.getMessage()); - } catch (IOException ioException) { - throw new JSONException(ioException.getMessage()); - } - - return mapOGCStructure; - } - - private static SpatialReference importSpatialReferenceFromGeoJson_(int importFlags, JSONObject crsJSONObject) - throws JSONException, JsonParseException, IOException { - - SpatialReference spatial_reference = null; - boolean b_crs_found = false, b_crsURN_found = false; - - Object opt = crsJSONObject.opt("crs"); - - if (opt != null) { - b_crs_found = true; - JSONObject crs_object = new JSONObject(); - crs_object.put("crs", opt); - JsonValueReader json_iterator = new JsonValueReader(crs_object); - json_iterator.nextToken(); - json_iterator.nextToken(); - return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); - } - - opt = crsJSONObject.opt("crsURN"); - - if (opt != null) { - b_crsURN_found = true; - JSONObject crs_object = new JSONObject(); - crs_object.put("crsURN", opt); - JsonValueReader json_iterator = new JsonValueReader(crs_object); - json_iterator.nextToken(); - json_iterator.nextToken(); - return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); - } - - if ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0) { - spatial_reference = SpatialReference.create(4326); - } - - return spatial_reference; - } - - /* - private static Geometry importGeometryFromGeoJson_(int importFlags, Geometry.Type type, - JSONObject geometryJSONObject) throws JSONException { - String typeString = geometryJSONObject.getString("type"); - JSONArray coordinateArray = getJSONArray(geometryJSONObject, "coordinates"); - if (typeString.equalsIgnoreCase("MultiPolygon")) { - if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return polygonTaggedText_(true, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("MultiLineString")) { - if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return lineStringTaggedText_(true, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("MultiPoint")) { - if (type != Geometry.Type.MultiPoint && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return multiPointTaggedText_(importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("Polygon")) { - if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return polygonTaggedText_(false, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("LineString")) { - if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return lineStringTaggedText_(false, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("Point")) { - if (type != Geometry.Type.Point && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return pointTaggedText_(importFlags, coordinateArray); - } else { - return null; - } - } - - private static Geometry polygonTaggedText_(boolean bMultiPolygon, int importFlags, JSONArray coordinateArray) - throws JSONException { - MultiPath multiPath; - MultiPathImpl multiPathImpl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - AttributeStreamOfInt32 paths; - AttributeStreamOfInt8 path_flags; - position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); - multiPath = new Polygon(); - multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiPolygon) { - pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, coordinateArray); - } else { - pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, coordinateArray); - } - if (pointCount != 0) { - assert(2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); - multiPathImpl.setPathStreamRef(paths); - multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); - } - if (ms != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); - } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(path_flags); - for (int i = 0; i < path_flags_clone.size() - 1; i++) { - if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should - // be - // clockwise - if (!InternalUtils.isClockwiseRing(multiPathImpl, i)) - multiPathImpl.reversePath(i); // make clockwise - } else {// Should be counter-clockwise - if (InternalUtils.isClockwiseRing(multiPathImpl, i)) - multiPathImpl.reversePath(i); // make counter-clockwise - } - } - multiPathImpl.setPathFlagsStreamRef(path_flags_clone); - } - if ((importFlags & (int) GeoJsonImportFlags.geoJsonImportNonTrusted) == 0) { - multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, false); - } - multiPathImpl.setDirtyOGCFlags(false); - return multiPath; - } - - private static Geometry lineStringTaggedText_(boolean bMultiLineString, int importFlags, JSONArray coordinateArray) - throws JSONException { - MultiPath multiPath; - MultiPathImpl multiPathImpl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - AttributeStreamOfInt32 paths; - AttributeStreamOfInt8 path_flags; - position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); - multiPath = new Polyline(); - multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiLineString) { - pointCount = multiLineStringText_(zs, ms, position, paths, path_flags, coordinateArray); - } else { - pointCount = lineStringText_(false, zs, ms, position, paths, path_flags, coordinateArray); - } - if (pointCount != 0) { - assert(2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); - multiPathImpl.setPathStreamRef(paths); - multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); - } - if (ms != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); - } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - } - return multiPath; - } - - private static Geometry multiPointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { - MultiPoint multiPoint; - MultiPointImpl multiPointImpl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); - multiPoint = new MultiPoint(); - multiPointImpl = (MultiPointImpl) multiPoint._getImpl(); - int pointCount = multiPointText_(zs, ms, position, coordinateArray); - if (pointCount != 0) { - assert(2 * pointCount == position.size()); - multiPointImpl.resize(pointCount); - multiPointImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); - multiPointImpl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); - } - return multiPoint; - } - - private static Geometry pointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { - Point point = new Point(); - int length = coordinateArray.length(); - if (length == 0) { - point.setEmpty(); - return point; - } - point.setXY(getDouble_(coordinateArray, 0), getDouble_(coordinateArray, 1)); - return point; - } - - private static int multiPolygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - JSONArray coordinateArray) throws JSONException { - // At start of MultiPolygonText - int totalPointCount = 0; - int length = coordinateArray.length(); - if (length == 0) - return totalPointCount; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // polygon, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - - // At start of PolygonText - totalPointCount = polygonText_(zs, ms, position, paths, path_flags, totalPointCount, subArray); - } - return totalPointCount; - } - - private static int multiLineStringText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - JSONArray coordinateArray) throws JSONException { - // At start of MultiLineStringText - int totalPointCount = 0; - int length = coordinateArray.length(); - if (length == 0) - return totalPointCount; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // line string, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - - // At start of LineStringText - totalPointCount += lineStringText_(false, zs, ms, position, paths, path_flags, subArray); - } - return totalPointCount; - } - - private static int multiPointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - JSONArray coordinateArray) throws JSONException { - // At start of MultiPointText - int pointCount = 0; - for (int current = 0; current < coordinateArray.length(); current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // point, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - pointCount += pointText_(zs, ms, position, subArray); - } - return pointCount; - } - - private static int polygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, int totalPointCount, - JSONArray coordinateArray) throws JSONException { - // At start of PolygonText - int length = coordinateArray.length(); - if (length == 0) { - return totalPointCount; - } - boolean bFirstLineString = true; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // line string, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - // At start of LineStringText - int pointCount = lineStringText_(true, zs, ms, position, paths, path_flags, subArray); - if (pointCount != 0) { - if (bFirstLineString) { - bFirstLineString = false; - path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumOGCStartPolygon); - } - path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumClosed); - totalPointCount += pointCount; - } - } - return totalPointCount; + public MapOGCStructure executeOGC(int import_flags, String geoJsonString, + ProgressTracker progress_tracker) throws JsonGeometryException { + return executeOGC(import_flags, JsonParserReader.createFromString(geoJsonString), + progress_tracker); } - private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - JSONArray coordinateArray) throws JSONException { - // At start of LineStringText - int pointCount = 0; - int length = coordinateArray.length(); - if (length == 0) - return pointCount; - boolean bStartPath = true; - double startX = NumberUtils.TheNaN; - double startY = NumberUtils.TheNaN; - double startZ = NumberUtils.TheNaN; - double startM = NumberUtils.TheNaN; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // single point, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - // At start of x - double x = getDouble_(subArray, 0); - double y = getDouble_(subArray, 1); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - boolean bAddPoint = true; - if (bRing && pointCount >= 2 && current == length - 1) {// If the - // last - // point in - // the ring - // is not - // equal to - // the start - // point, - // then - // let's add - // it. - if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils.isNaN(x))) - && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils.isNaN(y)))) { - bAddPoint = false; - } - } - if (bAddPoint) { - if (bStartPath) { - bStartPath = false; - startX = x; - startY = y; - startZ = z; - startM = m; - } - pointCount++; - addToStreams_(zs, ms, position, x, y, z, m); - } - } - if (pointCount == 1) { - pointCount++; - addToStreams_(zs, ms, position, startX, startY, startZ, startM); - } - paths.add(position.size() / 2); - path_flags.add((byte) 0); - return pointCount; + public MapOGCStructure executeOGC(int import_flags, + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { + MapOGCStructure mapOGCStructure = OperatorImportFromGeoJsonHelper.importFromGeoJson( + import_flags, Geometry.Type.Unknown, json_iterator, + progress_tracker, false, 0); + + //This is to restore legacy behavior when we always return a geometry collection of one element. + MapOGCStructure res = new MapOGCStructure(); + res.m_ogcStructure = new OGCStructure(); + res.m_ogcStructure.m_type = 0; + res.m_ogcStructure.m_structures = new ArrayList(); + res.m_ogcStructure.m_structures.add(mapOGCStructure.m_ogcStructure); + res.m_spatialReference = mapOGCStructure.m_spatialReference; + return res; } - private static int pointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - JSONArray coordinateArray) throws JSONException { - // At start of PointText - int length = coordinateArray.length(); - if (length == 0) - return 0; - // At start of x - double x = getDouble_(coordinateArray, 0); - double y = getDouble_(coordinateArray, 1); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - addToStreams_(zs, ms, position, x, y, z, m); - return 1; - } - - private static void addToStreams_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - double x, double y, double z, double m) { - position.add(x); - position.add(y); - if (zs != null) - zs.add(z); - if (ms != null) - ms.add(m); - } - - private static double getDouble_(JSONArray coordinateArray, int index) throws JSONException { - if (index < 0 || index >= coordinateArray.length()) { - throw new IllegalArgumentException(""); - } - if (coordinateArray.isNull(index)) { - return NumberUtils.TheNaN; - } - if (coordinateArray.optDouble(index, NumberUtils.TheNaN) != NumberUtils.TheNaN) { - return coordinateArray.getDouble(index); - } - throw new IllegalArgumentException(""); - }*/ } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java index 6a88d05c..93f30da5 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,15 +24,6 @@ package com.esri.core.geometry; -import java.io.IOException; - -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONObject; -import org.json.JSONException; - -import com.esri.core.geometry.Operator.Type; - /** *Import from JSON format. */ @@ -48,29 +39,20 @@ public Type getType() { * @return Returns a MapGeometryCursor. */ abstract MapGeometryCursor execute(Geometry.Type type, - JsonParserCursor jsonParserCursor); + JsonReaderCursor jsonReaderCursor); /** *Performs the ImportFromJson operation on a single Json string *@return Returns a MapGeometry. */ public abstract MapGeometry execute(Geometry.Type type, - JsonParser jsonParser); + JsonReader jsonReader); /** *Performs the ImportFromJson operation on a single Json string *@return Returns a MapGeometry. */ - public abstract MapGeometry execute(Geometry.Type type, String string) - throws JsonParseException, IOException; - - /** - *Performs the ImportFromJson operation on a JSONObject - *@return Returns a MapGeometry. - */ - public abstract MapGeometry execute(Geometry.Type type, JSONObject jsonObject) - throws JSONException, IOException; - + public abstract MapGeometry execute(Geometry.Type type, String string); public static OperatorImportFromJson local() { return (OperatorImportFromJson) OperatorFactoryLocal.getInstance() diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index 8c267d7a..2971f441 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,18 +26,15 @@ import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; import com.esri.core.geometry.VertexDescription.Semantics; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; class OperatorImportFromJsonCursor extends MapGeometryCursor { - JsonParserCursor m_inputJsonParsers; + JsonReaderCursor m_inputJsonParsers; int m_type; int m_index; - public OperatorImportFromJsonCursor(int type, JsonParserCursor jsonParsers) { + public OperatorImportFromJsonCursor(int type, JsonReaderCursor jsonParsers) { m_index = -1; if (jsonParsers == null) throw new IllegalArgumentException(); @@ -53,10 +50,10 @@ public int getGeometryID() { @Override public MapGeometry next() { - JsonParser jsonParser; + JsonReader jsonParser; if ((jsonParser = m_inputJsonParsers.next()) != null) { m_index = m_inputJsonParsers.getID(); - return importFromJsonParser(m_type, new JsonParserReader(jsonParser)); + return importFromJsonParser(m_type, jsonParser); } return null; } @@ -108,26 +105,26 @@ static MapGeometry importFromJsonParser(int gt, JsonReader parser) { Geometry geometry = null; SpatialReference spatial_reference = null; - while (parser.nextToken() != JsonToken.END_OBJECT) { + while (parser.nextToken() != JsonReader.Token.END_OBJECT) { String name = parser.currentString(); parser.nextToken(); if (!bFoundSpatial_reference && name.equals("spatialReference")) { bFoundSpatial_reference = true; - if (parser.currentToken() == JsonToken.START_OBJECT) { + if (parser.currentToken() == JsonReader.Token.START_OBJECT) { spatial_reference = SpatialReference.fromJson(parser); } else { - if (parser.currentToken() != JsonToken.VALUE_NULL) + if (parser.currentToken() != JsonReader.Token.VALUE_NULL) throw new GeometryException( "failed to parse spatial reference: object or null is expected"); } } else if (!bFoundHasZ && name.equals("hasZ")) { bFoundHasZ = true; - bHasZ = (parser.currentToken() == JsonToken.VALUE_TRUE); + bHasZ = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); } else if (!bFoundHasM && name.equals("hasM")) { bFoundHasM = true; - bHasM = (parser.currentToken() == JsonToken.VALUE_TRUE); + bHasM = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); } else if (!bFoundPolygon && name.equals("rings") && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { @@ -308,15 +305,13 @@ public static MapGeometry fromJsonToMultiPoint(JsonReader parser) return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); } - private static void windup(JsonReader parser) throws Exception, - JsonParseException { + private static void windup(JsonReader parser) { parser.skipChildren(); } - private static double readDouble(JsonReader parser) throws Exception, - JsonParseException { - if (parser.currentToken() == JsonToken.VALUE_NULL - || parser.currentToken() == JsonToken.VALUE_STRING + private static double readDouble(JsonReader parser) { + if (parser.currentToken() == JsonReader.Token.VALUE_NULL + || parser.currentToken() == JsonReader.Token.VALUE_STRING && parser.currentString().equals("NaN")) return NumberUtils.NaN(); else @@ -325,7 +320,7 @@ private static double readDouble(JsonReader parser) throws Exception, private static Geometry importFromJsonMultiPoint(JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.currentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array of vertices is expected"); @@ -340,13 +335,13 @@ private static Geometry importFromJsonMultiPoint(JsonReader parser, // At start of rings int sz; double[] buf = new double[4]; - while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.currentToken() != JsonToken.START_ARRAY) + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); sz = 0; - while (parser.nextToken() != JsonToken.END_ARRAY) { + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { buf[sz++] = readDouble(parser); } @@ -408,7 +403,7 @@ else if (c < 16) private static Geometry importFromJsonMultiPath(boolean b_polygon, JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.currentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipath: array of array of vertices is expected"); @@ -436,8 +431,8 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, int requiredSize = b_polygon ? 3 : 2; // At start of rings - while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.currentToken() != JsonToken.START_ARRAY) + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipath: ring/path array is expected"); @@ -447,13 +442,13 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, int szstart = 0; parser.nextToken(); - while (parser.currentToken() != JsonToken.END_ARRAY) { - if (parser.currentToken() != JsonToken.START_ARRAY) + while (parser.currentToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); sz = 0; - while (parser.nextToken() != JsonToken.END_ARRAY) { + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { buf[sz++] = readDouble(parser); } @@ -522,7 +517,7 @@ else if (c < 16) point_count++; pathPointCount++; } while (pathPointCount < requiredSize - && parser.currentToken() == JsonToken.END_ARRAY); + && parser.currentToken() == JsonReader.Token.END_ARRAY); } if (b_polygon && pathPointCount > requiredSize && sz == szstart diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java index 4e41a491..55d94171 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,46 +23,20 @@ */ package com.esri.core.geometry; -import java.io.IOException; - -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONObject; -import org.json.JSONException; - -import com.esri.core.geometry.ogc.OGCGeometry; - class OperatorImportFromJsonLocal extends OperatorImportFromJson { @Override public MapGeometryCursor execute(Geometry.Type type, - JsonParserCursor jsonParserCursor) { + JsonReaderCursor jsonParserCursor) { return new OperatorImportFromJsonCursor(type.value(), jsonParserCursor); } @Override - public MapGeometry execute(Geometry.Type type, JsonParser jsonParser) { - SimpleJsonParserCursor jsonParserCursor = new SimpleJsonParserCursor( - jsonParser); - OperatorImportFromJsonCursor cursor = new OperatorImportFromJsonCursor( - type.value(), jsonParserCursor); - return cursor.next(); + public MapGeometry execute(Geometry.Type type, JsonReader jsonParser) { + return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), jsonParser); } @Override - public MapGeometry execute(Geometry.Type type, String string) - throws JsonParseException, IOException { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParserPt = factory.createJsonParser(string); - jsonParserPt.nextToken(); - return execute(type, jsonParserPt); + public MapGeometry execute(Geometry.Type type, String string) { + return execute(type, JsonParserReader.createFromString(string)); } - @Override - public MapGeometry execute(Geometry.Type type, JSONObject jsonObject) - throws JSONException, IOException { - if (jsonObject == null) - return null; - - return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), new JsonValueReader(jsonObject)); - } } diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java similarity index 78% rename from src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java rename to src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java index 5f034a82..9a0793fc 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,23 +23,21 @@ */ package com.esri.core.geometry; -import org.codehaus.jackson.JsonParser; +class SimpleJsonReaderCursor extends JsonReaderCursor { -class SimpleJsonParserCursor extends JsonParserCursor { - - JsonParser m_jsonParser; - JsonParser[] m_jsonParserArray; + JsonReader m_jsonParser; + JsonReader[] m_jsonParserArray; int m_index; int m_count; - public SimpleJsonParserCursor(JsonParser jsonString) { + public SimpleJsonReaderCursor(JsonReader jsonString) { m_jsonParser = jsonString; m_index = -1; m_count = 1; } - public SimpleJsonParserCursor(JsonParser[] jsonStringArray) { + public SimpleJsonReaderCursor(JsonReader[] jsonStringArray) { m_jsonParserArray = jsonStringArray; m_index = -1; m_count = jsonStringArray.length; @@ -51,7 +49,7 @@ public int getID() { } @Override - public JsonParser next() { + public JsonReader next() { if (m_index < m_count - 1) { m_index++; return m_jsonParser != null ? m_jsonParser diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 39b1e90a..44fe3912 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,12 +26,11 @@ import java.io.ObjectStreamException; import java.io.Serializable; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.SpatialReferenceSerializer; import com.esri.core.geometry.VertexDescription; +import com.fasterxml.jackson.core.JsonParser; /** * A class that represents the spatial reference for the geometry. @@ -91,7 +90,11 @@ public static SpatialReference fromJson(JsonParser parser) throws Exception { return fromJson(new JsonParserReader(parser)); } - static SpatialReference fromJson(JsonReader parser) throws Exception { + public static SpatialReference fromJson(String string) throws Exception { + return fromJson(JsonParserReader.createFromString(string)); + } + + public static SpatialReference fromJson(JsonReader parser) throws Exception { // Note this class is processed specially: it is expected that the // iterator points to the first element of the SR object. boolean bFoundWkid = false; @@ -105,34 +108,34 @@ static SpatialReference fromJson(JsonReader parser) throws Exception { int vcs_wkid = -1; int latestVcsWkid = -1; String wkt = null; - while (parser.nextToken() != JsonToken.END_OBJECT) { + while (parser.nextToken() != JsonReader.Token.END_OBJECT) { String name = parser.currentString(); parser.nextToken(); if (!bFoundWkid && name.equals("wkid")) { bFoundWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) wkid = parser.currentIntValue(); } else if (!bFoundLatestWkid && name.equals("latestWkid")) { bFoundLatestWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) latestWkid = parser.currentIntValue(); } else if (!bFoundWkt && name.equals("wkt")) { bFoundWkt = true; - if (parser.currentToken() == JsonToken.VALUE_STRING) + if (parser.currentToken() == JsonReader.Token.VALUE_STRING) wkt = parser.currentString(); } else if (!bFoundVcsWkid && name.equals("vcsWkid")) { bFoundVcsWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) vcs_wkid = parser.currentIntValue(); } else if (!bFoundLatestVcsWkid && name.equals("latestVcsWkid")) { bFoundLatestVcsWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) latestVcsWkid = parser.currentIntValue(); } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 04b2df77..49197b83 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -5,42 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; - -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.Envelope1D; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryCursor; -import com.esri.core.geometry.GeometryCursorAppend; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.MapGeometry; -import com.esri.core.geometry.MapOGCStructure; -import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.NumberUtils; -import com.esri.core.geometry.OGCStructure; -import com.esri.core.geometry.Operator; -import com.esri.core.geometry.OperatorBuffer; -import com.esri.core.geometry.OperatorConvexHull; -import com.esri.core.geometry.OperatorExportToWkb; -import com.esri.core.geometry.OperatorExportToGeoJson; -import com.esri.core.geometry.OperatorFactoryLocal; -import com.esri.core.geometry.OperatorImportFromESRIShape; -import com.esri.core.geometry.OperatorImportFromGeoJson; -import com.esri.core.geometry.OperatorImportFromWkb; -import com.esri.core.geometry.OperatorImportFromWkt; -import com.esri.core.geometry.OperatorIntersection; -import com.esri.core.geometry.OperatorSimplify; -import com.esri.core.geometry.OperatorSimplifyOGC; -import com.esri.core.geometry.OperatorUnion; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.Polygon; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.SimpleGeometryCursor; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.VertexDescription; +import com.esri.core.geometry.*; /** * OGC Simple Feature Access specification v.1.2.1 @@ -493,18 +458,13 @@ public static OGCGeometry fromEsriShape(ByteBuffer buffer) { SpatialReference.create(4326)); } - public static OGCGeometry fromJson(String string) - throws JsonParseException, IOException { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParserPt = factory.createJsonParser(string); - jsonParserPt.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + public static OGCGeometry fromJson(String string) { + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(string)); return OGCGeometry.createFromEsriGeometry(mapGeom.getGeometry(), mapGeom.getSpatialReference()); } - public static OGCGeometry fromGeoJson(String string) - throws JSONException { + public static OGCGeometry fromGeoJson(String string) { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index c1af6866..62ddbc37 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -4,9 +4,6 @@ import java.io.FileNotFoundException; import java.util.Scanner; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; - public class GeometryUtils { public static String getGeometryType(Geometry geomIn) { // there are five types: esriGeometryPoint @@ -29,12 +26,8 @@ public static String getGeometryType(Geometry geomIn) { } static Geometry getGeometryFromJSon(String jsonStr) { - JsonFactory jf = new JsonFactory(); - try { - JsonParser jp = jf.createJsonParser(jsonStr); - jp.nextToken(); - Geometry geom = GeometryEngine.jsonToGeometry(jp).getGeometry(); + Geometry geom = GeometryEngine.jsonToGeometry(jsonStr).getGeometry(); return geom; } catch (Exception ex) { return null; diff --git a/src/test/java/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java index 420718d4..b38aeec4 100644 --- a/src/test/java/com/esri/core/geometry/TestCommonMethods.java +++ b/src/test/java/com/esri/core/geometry/TestCommonMethods.java @@ -5,8 +5,6 @@ import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; import junit.framework.TestCase; import org.junit.Test; @@ -237,15 +235,8 @@ public static Object readObjectFromFile(String fileName) { } public static MapGeometry fromJson(String jsonString) { - JsonFactory factory = new JsonFactory(); try { - JsonParser jsonParser = factory.createJsonParser(jsonString); - jsonParser.nextToken(); - OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal - .getInstance().getOperator( - Operator.Type.ImportFromJson); - - return importer.execute(Geometry.Type.Unknown, jsonParser); + return OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); } catch (Exception ex) { } diff --git a/src/test/java/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java index 71c811e9..4488b3ef 100644 --- a/src/test/java/com/esri/core/geometry/TestContains.java +++ b/src/test/java/com/esri/core/geometry/TestContains.java @@ -1,12 +1,15 @@ package com.esri.core.geometry; -import java.io.IOException; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; + +import java.io.IOException; + import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; + public class TestContains extends TestCase { @Override protected void setUp() throws Exception { @@ -19,11 +22,10 @@ protected void tearDown() throws Exception { } @Test - public static void testContainsFailureCR186456() throws JsonParseException, - IOException { + public static void testContainsFailureCR186456() throws JsonParseException, IOException { String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; JsonFactory jsonFactory = new JsonFactory(); - JsonParser jsonParser = jsonFactory.createJsonParser(str); + JsonParser jsonParser = jsonFactory.createParser(str); MapGeometry mg = GeometryEngine.jsonToGeometry(jsonParser); boolean res = GeometryEngine.contains(mg.getGeometry(), mg.getGeometry(), null); diff --git a/src/test/java/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java index b531bb17..efcdaeeb 100644 --- a/src/test/java/com/esri/core/geometry/TestDistance.java +++ b/src/test/java/com/esri/core/geometry/TestDistance.java @@ -1,10 +1,6 @@ package com.esri.core.geometry; -import java.io.IOException; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import org.junit.Test; public class TestDistance extends TestCase { @@ -146,17 +142,13 @@ private static Point makePoint() { } @Test - public static void testDistanceWithNullSpatialReference() - throws JsonParseException, IOException { + public static void testDistanceWithNullSpatialReference() { // There was a bug that distance op did not work with null Spatial // Reference. String str1 = "{\"paths\":[[[-117.138791850991,34.017492675023],[-117.138762336971,34.0174925550462]]]}"; String str2 = "{\"paths\":[[[-117.138867827972,34.0174854109623],[-117.138850197027,34.0174929160126],[-117.138791850991,34.017492675023]]]}"; - JsonFactory jsonFactory = new JsonFactory(); - JsonParser jsonParser1 = jsonFactory.createJsonParser(str1); - JsonParser jsonParser2 = jsonFactory.createJsonParser(str2); - MapGeometry geom1 = GeometryEngine.jsonToGeometry(jsonParser1); - MapGeometry geom2 = GeometryEngine.jsonToGeometry(jsonParser2); + MapGeometry geom1 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str1)); + MapGeometry geom2 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str2)); double distance = GeometryEngine.distance(geom1.getGeometry(), geom2.getGeometry(), null); assertTrue(distance == 0); diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 5ef3157e..aa480b26 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -27,12 +27,10 @@ import com.esri.core.geometry.ogc.OGCMultiPoint; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCPolygon; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; -import org.json.JSONObject; import org.junit.Test; import java.io.IOException; @@ -228,10 +226,10 @@ public void testPolygonWithHoleReversed() { public void testMultiPolygon() throws IOException { JsonFactory jsonFactory = new JsonFactory(); - String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; + //String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; - JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); + JsonParser parser = jsonFactory.createParser(esriJsonPolygon); MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); @@ -244,9 +242,8 @@ public void testMultiPolygon() throws IOException { } - @Deprecated @Test - public void testEmptyPolygon() throws JSONException { + public void testEmptyPolygon() { Polygon p = new Polygon(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); @@ -386,16 +383,6 @@ public void testGeometryCollection() { geoms, sr); String s2 = collection.asGeoJson(); - JSONObject json = null; - boolean valid = false; - try { - json = new JSONObject(s2); - valid = true; - } catch (Exception e) { - } - - assertTrue(valid); - assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson()); } @@ -435,7 +422,7 @@ public void testEnvelopeGeometryEngine() { } @Test - public void testOldCRS() throws JSONException { + public void testOldCRS() { String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}"; MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null); String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry()); diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index ce4f81dc..237abb4c 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,12 +1,13 @@ package com.esri.core.geometry; import java.io.IOException; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import junit.framework.TestCase; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; + public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { @Override protected void setUp() throws Exception { @@ -43,7 +44,7 @@ boolean testPoint() throws JsonParseException, IOException { Point pointEmpty = new Point(); { JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWebMerc1, point1)); MapGeometry pointWebMerc1MP = GeometryEngine .jsonToGeometry(pointWebMerc1Parser); @@ -59,7 +60,7 @@ boolean testPoint() throws JsonParseException, IOException { bAnswer = false; } - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + pointWebMerc1Parser = factory.createParser(GeometryEngine .geometryToJson(null, point1)); pointWebMerc1MP = GeometryEngine .jsonToGeometry(pointWebMerc1Parser); @@ -73,11 +74,11 @@ boolean testPoint() throws JsonParseException, IOException { String pointEmptyString = GeometryEngine.geometryToJson( spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + pointWebMerc1Parser = factory.createParser(pointEmptyString); } JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWebMerc2, point1)); MapGeometry pointWebMerc2MP = GeometryEngine .jsonToGeometry(pointWebMerc2Parser); @@ -94,7 +95,7 @@ boolean testPoint() throws JsonParseException, IOException { { JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, point1)); MapGeometry pointWgs84MP = GeometryEngine .jsonToGeometry(pointWgs84Parser); @@ -135,7 +136,7 @@ boolean testPoint() throws JsonParseException, IOException { {// import String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); Point pt = (Point) map_pt.getGeometry(); assertTrue(pt.getX() == 0.0); @@ -146,7 +147,7 @@ boolean testPoint() throws JsonParseException, IOException { { String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); Point pt = (Point) map_pt.getGeometry(); assertTrue(pt.isEmpty()); @@ -169,7 +170,7 @@ boolean testMultiPoint() throws JsonParseException, IOException { { String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, multiPoint1); - JsonParser mPointWgs84Parser = factory.createJsonParser(s); + JsonParser mPointWgs84Parser = factory.createParser(s); MapGeometry mPointWgs84MP = GeometryEngine .jsonToGeometry(mPointWgs84Parser); assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP @@ -210,7 +211,7 @@ boolean testMultiPoint() throws JsonParseException, IOException { { String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; MapGeometry mp = GeometryEngine.jsonToGeometry(factory - .createJsonParser(points)); + .createParser(points)); MultiPoint multipoint = (MultiPoint) mp.getGeometry(); assertTrue(multipoint.getPointCount() == 4); Point2D point2d; @@ -248,7 +249,7 @@ boolean testPolyline() throws JsonParseException, IOException { { JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, polyline)); MapGeometry mPolylineWGS84MP = GeometryEngine .jsonToGeometry(polylinePathsWgs84Parser); @@ -307,7 +308,7 @@ boolean testPolyline() throws JsonParseException, IOException { { String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createJsonParser(paths)); + .createParser(paths)); Polyline p = (Polyline) mapGeometry.getGeometry(); assertTrue(p.getPathCount() == 2); @SuppressWarnings("unused") @@ -341,7 +342,7 @@ boolean testPolygon() throws JsonParseException, IOException { { JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, polygon)); MapGeometry mPolygonWGS84MP = GeometryEngine .jsonToGeometry(polygonPathsWgs84Parser); @@ -405,7 +406,7 @@ boolean testPolygon() throws JsonParseException, IOException { // Test Import Polygon from Polygon String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createJsonParser(rings)); + .createParser(rings)); Polygon p = (Polygon) mapGeometry.getGeometry(); @SuppressWarnings("unused") double area = p.calculateArea2D(); @@ -429,7 +430,7 @@ boolean testEnvelope() throws JsonParseException, IOException { { JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, envelope)); MapGeometry envelopeWGS84MP = GeometryEngine .jsonToGeometry(envelopeWGS84Parser); @@ -475,7 +476,7 @@ boolean testEnvelope() throws JsonParseException, IOException { {// import String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); Envelope env = (Envelope) map_env.getGeometry(); Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); @@ -488,7 +489,7 @@ boolean testEnvelope() throws JsonParseException, IOException { { String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); Envelope env = (Envelope) map_env.getGeometry(); Envelope2D e = new Envelope2D(); @@ -513,13 +514,13 @@ boolean testCR181369() throws JsonParseException, IOException { String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); + .createParser(jsonStringPointAndWKT); MapGeometry mapGeom2 = GeometryEngine .jsonToGeometry(jsonParserPointAndWKT); String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); + .createParser(jsonStringPointAndWKT2); MapGeometry mapGeom3 = GeometryEngine .jsonToGeometry(jsonParserPointAndWKT2); assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 1138c082..5fd97f10 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -4,8 +4,6 @@ import java.nio.ByteOrder; import junit.framework.TestCase; -import org.json.JSONException; -import org.json.JSONObject; import org.junit.Test; public class TestImportExport extends TestCase { @@ -1228,7 +1226,7 @@ public static void testImportExportWktPoint() { @Deprecated @Test - public static void testImportGeoJsonGeometryCollection() throws JSONException { + public static void testImportGeoJsonGeometryCollection() { OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); String geoJsonString; @@ -1305,8 +1303,7 @@ public static void testImportGeoJsonMultiPolygon() throws Exception { assertTrue(polygon.getPathCount() == 5); assertTrue(spatial_reference.getLatestID() == 3857); - JSONObject jsonObject = new JSONObject(geoJsonString); - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, jsonObject, null); + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); polygon = (Polygon) map_geometry.getGeometry(); spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 243f7c26..8abd75cf 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,13 +1,14 @@ package com.esri.core.geometry; import java.io.IOException; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; import junit.framework.TestCase; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { JsonFactory factory = new JsonFactory(); @@ -28,7 +29,7 @@ public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + "\"spatialReference\" : {\"wkt\" : \"\"}}"; - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg = factory.createParser(jsonStringPg); jsonParserPg.nextToken(); MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index a2214e5e..d6593115 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -4,14 +4,14 @@ import java.io.IOException; import java.util.Map; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONObject; import org.junit.Assert; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + public class TestJsonParser extends TestCase { JsonFactory factory = new JsonFactory(); @@ -34,7 +34,7 @@ protected void tearDown() throws Exception { public void test3DPoint() throws JsonParseException, IOException { String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 5b2ef6b3..3edf417c 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -11,9 +11,9 @@ import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; import com.esri.core.geometry.ogc.OGCPolygon; +import com.fasterxml.jackson.core.JsonParseException; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; import java.io.IOException; @@ -850,15 +850,7 @@ public void testMultiPointSinglePoint() { public void testWktMultiPolygon() { String restJson = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; MapGeometry g = null; - try { - g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); - } catch (JsonParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); String wkt = OperatorExportToWkt.local().execute(0, g.getGeometry(), null); assertTrue(wkt.equals("MULTIPOLYGON (((-100 -100, 100 -100, 100 100, -100 100, -100 -100), (-90 -90, 90 -90, -90 90, 90 90, -90 -90)), ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))")); } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index e34a14b7..8b918e9c 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -4,7 +4,6 @@ import junit.framework.TestCase; -import org.json.JSONException; import org.junit.Test; import com.esri.core.geometry.ogc.OGCGeometry; @@ -1205,7 +1204,7 @@ public void testReplaceNaNs() { } @Test - public void testPolygon2PolygonFails() throws IOException, JSONException { + public void testPolygon2PolygonFails() { OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory .getOperator(Operator.Type.ExportToGeoJson); @@ -1221,10 +1220,10 @@ public void testPolygon2PolygonFails() throws IOException, JSONException { } @Test - public void testPolygon2PolygonFails2() throws JSONException { + public void testPolygon2PolygonFails2() { String birminghamGeojson = GeometryEngine .geometryToGeoJson(birmingham()); - MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Polygon); Polygon polygon = (Polygon) returnedGeometry.getGeometry(); @@ -1232,10 +1231,10 @@ public void testPolygon2PolygonFails2() throws JSONException { } @Test - public void testPolygon2PolygonWorks() throws JSONException { + public void testPolygon2PolygonWorks() { String birminghamGeojson = GeometryEngine .geometryToGeoJson(birmingham()); - MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Polygon); Polygon polygon = (Polygon) returnedGeometry.getGeometry(); @@ -1243,7 +1242,7 @@ public void testPolygon2PolygonWorks() throws JSONException { } @Test - public void testPolygon2Polygon2Works() throws JSONException, IOException { + public void testPolygon2Polygon2Works() { String birminghamJson = GeometryEngine.geometryToJson(4326, birmingham()); MapGeometry returnedGeometry = GeometryEngine diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 5c00d8ad..041908cf 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -4,7 +4,6 @@ import junit.framework.TestCase; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; @@ -5498,7 +5497,7 @@ public void testDisjointCrash() { } @Test - public void testDisjointFail() throws JsonParseException, IOException { + public void testDisjointFail() { MapGeometry geometry1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"paths\":[[[3,3],[3,3]]],\"spatialReference\":{\"wkid\":4326}}"); MapGeometry geometry2 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[2,2],[2,4],[4,4],[4,2],[2,2]]],\"spatialReference\":{\"wkid\":4326}}"); OperatorDisjoint.local().accelerateGeometry(geometry1.getGeometry(), geometry1.getSpatialReference(), GeometryAccelerationDegree.enumMedium); diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index bdc2be0f..b7380851 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -9,10 +9,10 @@ import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; + public class TestSimplify extends TestCase { OperatorFactoryLocal factory = null; OperatorSimplify simplifyOp = null; @@ -1233,18 +1233,16 @@ public void testisSimpleOGC() { } @Test - public void testPolylineIsSimpleForOGC() throws IOException { + public void testPolylineIsSimpleForOGC() { OperatorImportFromJson importerJson = (OperatorImportFromJson) factory .getOperator(Operator.Type.ImportFromJson); OperatorSimplify simplify = (OperatorSimplify) factory .getOperator(Operator.Type.Simplify); - JsonFactory f = new JsonFactory(); - { String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(res); } @@ -1252,7 +1250,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self // intersection Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1260,7 +1258,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { { String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(res); } @@ -1271,7 +1269,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // self // tangent Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1284,7 +1282,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // a // point Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(res); } @@ -1298,7 +1296,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // one // point Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1308,7 +1306,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // lines // intersect Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1320,7 +1318,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // mid // point. Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1328,7 +1326,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { } @Test - public void testFillRule() throws JsonParseException, IOException { + public void testFillRule() { //self intersecting star shape MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); Polygon poly = (Polygon)mg.getGeometry(); diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 196e5e1e..0c84d883 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -2,9 +2,6 @@ import java.io.IOException; import java.nio.ByteBuffer; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import junit.framework.TestCase; import org.junit.Test; @@ -23,35 +20,27 @@ protected void tearDown() throws Exception { @Test public void testWKB() { - try { - // JSON -> GEOM -> WKB - - String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; - // String strPolygon1 = - // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; - - JsonFactory factory = new JsonFactory(); - JsonParser parser = factory.createJsonParser(strPolygon1); - parser.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); - Geometry geom = mapGeom.getGeometry(); - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, - // byteBuffer); - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - } catch (JsonParseException ex) { - } catch (IOException ex) { - } - + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); } @Test @@ -62,10 +51,7 @@ public void testWKB2() throws Exception { // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; - JsonFactory factory = new JsonFactory(); - JsonParser parser = factory.createJsonParser(strPolygon1); - parser.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); Geometry geom = mapGeom.getGeometry(); // simplifying geom From d6999a9d0bfff7113f331ced7165ab46cf341881 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 7 Jul 2017 14:27:29 -0700 Subject: [PATCH 043/116] Sergey/gitattribs (#137) --- .gitattributes | 9 + .../esri/core/geometry/GeometryEngine.java | 1823 +++++------ .../java/com/esri/core/geometry/TestCut.java | 1040 +++---- .../com/esri/core/geometry/TestFailed.java | 128 +- .../com/esri/core/geometry/TestGeodetic.java | 209 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 1130 +++---- .../esri/core/geometry/TestJSonGeometry.java | 95 +- .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 248 +- .../esri/core/geometry/TestJsonParser.java | 1328 ++++---- .../esri/core/geometry/TestSerialization.java | 760 ++--- .../com/esri/core/geometry/TestSimplify.java | 2688 ++++++++--------- .../esri/core/geometry/TestWKBSupport.java | 171 +- 12 files changed, 4821 insertions(+), 4808 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e1945df3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text=auto +*.java text +*.xml text + +# +# Binary specific files +# +resouces/* binary +*.jar binary diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 1339538c..6f729cea 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,911 +1,912 @@ -/* - Copyright 1995-2017 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; - -import com.fasterxml.jackson.core.JsonParser; - -/** - * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. - * Consider using OperatorXXX classes directly as they often provide more functionality and better performance. For example, some Operators accept - * GeometryCursor class that could be implemented to wrap a feature cursor and make it feed geometries directly into an Operator. - * Also, some operators provide a way to accelerate an operation by using Operator.accelerateGeometry method. - */ -public class GeometryEngine { - - private static OperatorFactoryLocal factory = OperatorFactoryLocal - .getInstance(); - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - */ - public static MapGeometry jsonToGeometry(JsonParser json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); - return geom; - } - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - */ - public static MapGeometry jsonToGeometry(JsonReader json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); - return geom; - } - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - * @throws IOException - * @throws JsonParseException - */ - public static MapGeometry jsonToGeometry(String json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); - return geom; - } - - /** - * Exports the specified geometry instance to it's JSON representation. - * - * See OperatorExportToJson. - * - * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, - * Geometry geometry) - * @param wkid - * The spatial reference Well Known ID to be used for the JSON - * representation. - * @param geometry - * The geometry to be exported to JSON. - * @return The JSON representation of the specified Geometry. - */ - public static String geometryToJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToJson( - wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } - - /** - * Exports the specified geometry instance to it's JSON representation. M - * and Z values are not imported from JSON representation. - * - * See OperatorExportToJson. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The JSON representation of the specified geometry. - */ - public static String geometryToJson(SpatialReference spatialReference, - Geometry geometry) { - OperatorExportToJson exporter = (OperatorExportToJson) factory - .getOperator(Operator.Type.ExportToJson); - - return exporter.execute(spatialReference, geometry); - } - - public static String geometryToGeoJson(Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory - .getOperator(Operator.Type.ExportToGeoJson); - - return exporter.execute(geometry); - } - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - * @throws IOException - * @throws JsonParseException - */ - public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { - MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); - return geom; - } - - /** - * Exports the specified geometry instance to its GeoJSON representation. - * - * See OperatorExportToGeoJson. - * - * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, - * Geometry geometry) - * - * @param wkid - * The spatial reference Well Known ID to be used for the GeoJSON - * representation. - * @param geometry - * The geometry to be exported to GeoJSON. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } - - /** - * Exports the specified geometry instance to it's JSON representation. - * - * See OperatorImportFromGeoJson. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - - return exporter.execute(spatialReference, geometry); - } - - /** - * Imports geometry from the ESRI shape file format. - * - * See OperatorImportFromESRIShape. - * - * @param esriShapeBuffer - * The buffer containing geometry in the ESRI shape file format. - * @param geometryType - * The required type of the Geometry to be imported. Use - * Geometry.Type.Unknown if the geometry type needs to be - * determined from the buffer content. - * @return The geometry or null if the buffer contains null shape. - * @throws GeometryException - * when the geometryType is not Geometry.Type.Unknown and the - * buffer contains geometry that cannot be converted to the - * given geometryType. or the buffer is corrupt. Another - * exception possible is IllegalArgumentsException. - */ - public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, - Geometry.Type geometryType) { - OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory - .getOperator(Operator.Type.ImportFromESRIShape); - return op - .execute( - ShapeImportFlags.ShapeImportNonTrusted, - geometryType, - ByteBuffer.wrap(esriShapeBuffer).order( - ByteOrder.LITTLE_ENDIAN)); - } - - /** - * Exports geometry to the ESRI shape file format. - * - * See OperatorExportToESRIShape. - * - * @param geometry - * The geometry to export. (null value is not allowed) - * @return Array containing the exported ESRI shape file. - */ - public static byte[] geometryToEsriShape(Geometry geometry) { - if (geometry == null) - throw new IllegalArgumentException(); - OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory - .getOperator(Operator.Type.ExportToESRIShape); - return op.execute(0, geometry).array(); - } - - /** - * Imports a geometry from a WKT string. - * - * See OperatorImportFromWkt. - * - * @param wkt The string containing the geometry in WKT format. - * @param importFlags Use the {@link WktImportFlags} interface. - * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. - * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the WKT string. - */ - public static Geometry geometryFromWkt(String wkt, int importFlags, - Geometry.Type geometryType) { - OperatorImportFromWkt op = (OperatorImportFromWkt) factory - .getOperator(Operator.Type.ImportFromWkt); - return op.execute(importFlags, geometryType, wkt, null); - } - - /** - * Exports a geometry to a string in WKT format. - * - * See OperatorExportToWkt. - * - * @param geometry The geometry to export. (null value is not allowed) - * @param exportFlags Use the {@link WktExportFlags} interface. - * @return A String containing the exported geometry in WKT format. - */ - public static String geometryToWkt(Geometry geometry, int exportFlags) { - OperatorExportToWkt op = (OperatorExportToWkt) factory - .getOperator(Operator.Type.ExportToWkt); - return op.execute(exportFlags, geometry, null); - } - - /** - * Constructs a new geometry by union an array of geometries. All inputs - * must be of the same type of geometries and share one spatial reference. - * - * See OperatorUnion. - * - * @param geometries - * The geometries to union. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry object representing the resultant union. - */ - public static Geometry union(Geometry[] geometries, - SpatialReference spatialReference) { - OperatorUnion op = (OperatorUnion) factory - .getOperator(Operator.Type.Union); - - SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometries, spatialReference, - null); - return result.next(); - } - - /** - * Creates the difference of two geometries. The dimension of geometry2 has - * to be equal to or greater than that of geometry1. - * - * See OperatorDifference. - * - * @param geometry1 - * The geometry being subtracted. - * @param substractor - * The geometry object to subtract from. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry of the differences. - */ - public static Geometry difference(Geometry geometry1, Geometry substractor, - SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory - .getOperator(Operator.Type.Difference); - Geometry result = op.execute(geometry1, substractor, spatialReference, - null); - return result; - } - - /** - * Creates the symmetric difference of two geometries. - * - * See OperatorSymmetricDifference. - * - * @param leftGeometry - * is one of the Geometry instances in the XOR operation. - * @param rightGeometry - * is one of the Geometry instances in the XOR operation. - * @param spatialReference - * The spatial reference of the geometries. - * @return Returns the result of the symmetric difference. - */ - public static Geometry symmetricDifference(Geometry leftGeometry, - Geometry rightGeometry, SpatialReference spatialReference) { - OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory - .getOperator(Operator.Type.SymmetricDifference); - Geometry result = op.execute(leftGeometry, rightGeometry, - spatialReference, null); - return result; - } - - /** - * Indicates if two geometries are equal. - * - * See OperatorEquals. - * - * @param geometry1 - * Geometry. - * @param geometry2 - * Geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if both geometry objects are equal. - */ - public static boolean equals(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorEquals op = (OperatorEquals) factory - .getOperator(Operator.Type.Equals); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * See OperatorDisjoint. - * - */ - public static boolean disjoint(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDisjoint op = (OperatorDisjoint) factory - .getOperator(Operator.Type.Disjoint); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Constructs the set-theoretic intersection between an array of geometries - * and another geometry. - * - * See OperatorIntersection (also for dimension specific intersection). - * - * @param inputGeometries - * An array of geometry objects. - * @param geometry - * The geometry object. - * @return Any array of geometry objects showing the intersection. - */ - static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( - geometry); - GeometryCursor result = op.execute(inputGeometriesCursor, - intersectorCursor, spatialReference, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); - return resultarr; - } - - /** - * Creates a geometry through intersection between two geometries. - * - * See OperatorIntersection. - * - * @param geometry1 - * The first geometry. - * @param intersector - * The geometry to intersect the first geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry created through intersection. - */ - public static Geometry intersect(Geometry geometry1, Geometry intersector, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - Geometry result = op.execute(geometry1, intersector, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry is within another geometry. - * - * See OperatorWithin. - * - * @param geometry1 - * The base geometry that is tested for within relationship to - * the other geometry. - * @param geometry2 - * The comparison geometry that is tested for the contains - * relationship to the other geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if the first geometry is within the other geometry. - */ - public static boolean within(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorWithin op = (OperatorWithin) factory - .getOperator(Operator.Type.Within); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry contains another geometry. - * - * See OperatorContains. - * - * @param geometry1 - * The geometry that is tested for the contains relationship to - * the other geometry.. - * @param geometry2 - * The geometry that is tested for within relationship to the - * other geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 contains geometry2. - */ - public static boolean contains(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorContains op = (OperatorContains) factory - .getOperator(Operator.Type.Contains); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry crosses another geometry. - * - * See OperatorCrosses. - * - * @param geometry1 - * The geometry to cross. - * @param geometry2 - * The geometry being crossed. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 crosses geometry2. - */ - public static boolean crosses(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorCrosses op = (OperatorCrosses) factory - .getOperator(Operator.Type.Crosses); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry touches another geometry. - * - * See OperatorTouches. - * - * @param geometry1 - * The geometry to touch. - * @param geometry2 - * The geometry to be touched. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 touches geometry2. - */ - public static boolean touches(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorTouches op = (OperatorTouches) factory - .getOperator(Operator.Type.Touches); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry overlaps another geometry. - * - * See OperatorOverlaps. - * - * @param geometry1 - * The geometry to overlap. - * @param geometry2 - * The geometry to be overlapped. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 overlaps geometry2. - */ - public static boolean overlaps(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorOverlaps op = (OperatorOverlaps) factory - .getOperator(Operator.Type.Overlaps); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if the given relation holds for the two geometries. - * - * See OperatorRelate. - * - * @param geometry1 - * The first geometry for the relation. - * @param geometry2 - * The second geometry for the relation. - * @param spatialReference - * The spatial reference of the geometries. - * @param relation - * The DE-9IM relation. - * @return TRUE if the given relation holds between geometry1 and geometry2. - */ - public static boolean relate(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference, String relation) { - OperatorRelate op = (OperatorRelate) factory - .getOperator(Operator.Type.Relate); - boolean result = op.execute(geometry1, geometry2, spatialReference, - relation, null); - return result; - } - - /** - * Calculates the 2D planar distance between two geometries. - * - * See OperatorDistance. - * - * @param geometry1 - * Geometry. - * @param geometry2 - * Geometry. - * @param spatialReference - * The spatial reference of the geometries. This parameter is not - * used and can be null. - * @return The distance between the two geometries. - */ - public static double distance(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDistance op = (OperatorDistance) factory - .getOperator(Operator.Type.Distance); - double result = op.execute(geometry1, geometry2, null); - return result; - } - - /** - * Calculates the clipped geometry from a target geometry using an envelope. - * - * See OperatorClip. - * - * @param geometry - * The geometry to be clipped. - * @param envelope - * The envelope used to clip. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry created by clipping. - */ - public static Geometry clip(Geometry geometry, Envelope envelope, - SpatialReference spatialReference) { - OperatorClip op = (OperatorClip) factory - .getOperator(Operator.Type.Clip); - Geometry result = op.execute(geometry, Envelope2D.construct( - envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), - envelope.getYMax()), spatialReference, null); - return result; - } - - /** - * Calculates the cut geometry from a target geometry using a polyline. For - * Polylines, all left cuts will be grouped together in the first Geometry, - * Right cuts and coincident cuts are grouped in the second Geometry, and - * each undefined cut, along with any uncut parts, are output as separate - * Polylines. For Polygons, all left cuts are grouped in the first Polygon, - * all right cuts are in the second Polygon, and each undefined cut, along - * with any left-over parts after cutting, are output as a separate Polygon. - * If there were no cuts then the array will be empty. An undefined cut will - * only be produced if a left cut or right cut was produced, and there was a - * part left over after cutting or a cut is bounded to the left and right of - * the cutter. - * - * See OperatorCut. - * - * @param cuttee - * The geometry to be cut. - * @param cutter - * The polyline to cut the geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return An array of geometries created from cutting. - */ - public static Geometry[] cut(Geometry cuttee, Polyline cutter, - SpatialReference spatialReference) { - if (cuttee == null || cutter == null) - return null; - - OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); - GeometryCursor cursor = op.execute(true, cuttee, cutter, - spatialReference, null); - ArrayList cutsList = new ArrayList(); - - Geometry geometry; - while ((geometry = cursor.next()) != null) { - if (!geometry.isEmpty()) { - cutsList.add(geometry); - } - } - - return cutsList.toArray(new Geometry[0]); - } - /** - * Calculates a buffer polygon for each geometry at each of the - * corresponding specified distances. It is assumed that all geometries have - * the same spatial reference. There is an option to union the - * returned geometries. - * - * See OperatorBuffer. - * - * @param geometries An array of geometries to be buffered. - * @param spatialReference The spatial reference of the geometries. - * @param distances The corresponding distances for the input geometries to be buffered. - * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. - * @return The buffer of the geometries. - */ - public static Polygon[] buffer(Geometry[] geometries, - SpatialReference spatialReference, double[] distances, - boolean toUnionResults) { - // initially assume distances are in unit of spatial reference - double[] bufferDistances = distances; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - - if (toUnionResults) { - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometriesCursor, - spatialReference, bufferDistances, toUnionResults, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add((Polygon) g); - } - Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); - return buffers; - } else { - Polygon[] buffers = new Polygon[geometries.length]; - for (int i = 0; i < geometries.length; i++) { - buffers[i] = (Polygon) op.execute(geometries[i], - spatialReference, bufferDistances[i], null); - } - return buffers; - } - } - - /** - * Calculates a buffer polygon of the geometry as specified by the - * distance input. The buffer is implemented in the xy-plane. - * - * See OperatorBuffer - * - * @param geometry Geometry to be buffered. - * @param spatialReference The spatial reference of the geometry. - * @param distance The specified distance for buffer. Same units as the spatial reference. - * @return The buffer polygon at the specified distances. - */ - public static Polygon buffer(Geometry geometry, - SpatialReference spatialReference, double distance) { - double bufferDistance = distance; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - Geometry result = op.execute(geometry, spatialReference, - bufferDistance, null); - return (Polygon) result; - } - - /** - * Calculates the convex hull geometry. - * - * See OperatorConvexHull. - * - * @param geometry The input geometry. - * @return Returns the convex hull. - * - * For a Point - returns the same point. For an Envelope - - * returns the same envelope. For a MultiPoint - If the point - * count is one, returns the same multipoint. If the point count - * is two, returns a polyline of the points. Otherwise computes - * and returns the convex hull polygon. For a Segment - returns a - * polyline consisting of the segment. For a Polyline - If - * consists of only one segment, returns the same polyline. - * Otherwise computes and returns the convex hull polygon. For a - * Polygon - If more than one path, or if the path isn't already - * convex, computes and returns the convex hull polygon. - * Otherwise returns the same polygon. - */ - public static Geometry convexHull(Geometry geometry) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - return op.execute(geometry, null); - } - - /** - * Calculates the convex hull. - * - * See OperatorConvexHull - * - * @param geometries - * The input geometry array. - * @param b_merge - * Put true if you want the convex hull of all the geometries in - * the array combined. Put false if you want the convex hull of - * each geometry in the array individually. - * @return Returns an array of convex hulls. If b_merge is true, the result - * will be a one element array consisting of the merged convex hull. - */ - public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( - geometries); - GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = cursor.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] output = new Geometry[resultGeoms.size()]; - - for (int i = 0; i < resultGeoms.size(); i++) - output[i] = resultGeoms.get(i); - - return output; - } - - /** - * Finds the coordinate of the geometry which is closest to the specified - * point. - * - * See OperatorProximity2D. - * - * @param inputPoint - * The point to find the nearest coordinate in the geometry for. - * @param geometry - * The geometry to consider. - * @return Proximity2DResult containing the nearest coordinate. - */ - public static Proximity2DResult getNearestCoordinate(Geometry geometry, - Point inputPoint, boolean bTestPolygonInterior) { - - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestCoordinate(geometry, - inputPoint, bTestPolygonInterior); - return result; - } - - /** - * Finds nearest vertex on the geometry which is closed to the specified - * point. - * - * See OperatorProximity2D. - * - * @param inputPoint - * The point to find the nearest vertex of the geometry for. - * @param geometry - * The geometry to consider. - * @return Proximity2DResult containing the nearest vertex. - */ - public static Proximity2DResult getNearestVertex(Geometry geometry, - Point inputPoint) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestVertex(geometry, - inputPoint); - return result; - } - - /** - * Finds all vertices in the given distance from the specified point, sorted - * from the closest to the furthest. - * - * See OperatorProximity2D. - * - * @param inputPoint - * The point to start from. - * @param geometry - * The geometry to consider. - * @param searchRadius - * The search radius. - * @param maxVertexCountToReturn - * The maximum number number of vertices to return. - * @return Proximity2DResult containing the array of nearest vertices. - */ - public static Proximity2DResult[] getNearestVertices(Geometry geometry, - Point inputPoint, double searchRadius, int maxVertexCountToReturn) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - - Proximity2DResult[] results = proximity.getNearestVertices(geometry, - inputPoint, searchRadius, maxVertexCountToReturn); - - return results; - } - - /** - * Performs the simplify operation on the geometry. - * - * See OperatorSimplify and See OperatorSimplifyOGC. - * - * @param geometry - * The geometry to be simplified. - * @param spatialReference - * The spatial reference of the geometry to be simplified. - * @return The simplified geometry. - */ - public static Geometry simplify(Geometry geometry, - SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - Geometry result = op.execute(geometry, spatialReference, false, null); - return result; - } - - /** - * Checks if the Geometry is simple. - * - * See OperatorSimplify. - * - * @param geometry - * The geometry to be checked. - * @param spatialReference - * The spatial reference of the geometry. - * @return TRUE if the geometry is simple. - */ - static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); - return result; - } - - /** - * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's - * surface is approximated by a spheroid. The function returns the shortest distance between two points on the - * WGS84 spheroid. - * @param ptFrom The "from" point: long, lat in degrees. - * @param ptTo The "to" point: long, lat in degrees. - * @return The geodesic distance between two points in meters. - */ - public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { - return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); - } -} +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + +import com.fasterxml.jackson.core.JsonParser; + +/** + * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. + * Consider using OperatorXXX classes directly as they often provide more functionality and better performance. For example, some Operators accept + * GeometryCursor class that could be implemented to wrap a feature cursor and make it feed geometries directly into an Operator. + * Also, some operators provide a way to accelerate an operation by using Operator.accelerateGeometry method. + */ +public class GeometryEngine { + + private static OperatorFactoryLocal factory = OperatorFactoryLocal + .getInstance(); + + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonParser json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); + return geom; + } + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonReader json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry jsonToGeometry(String json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + + /** + * Exports the specified geometry instance to it's JSON representation. + * + * See OperatorExportToJson. + * + * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, + * Geometry geometry) + * @param wkid + * The spatial reference Well Known ID to be used for the JSON + * representation. + * @param geometry + * The geometry to be exported to JSON. + * @return The JSON representation of the specified Geometry. + */ + public static String geometryToJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToJson( + wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. M + * and Z values are not imported from JSON representation. + * + * See OperatorExportToJson. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The JSON representation of the specified geometry. + */ + public static String geometryToJson(SpatialReference spatialReference, + Geometry geometry) { + OperatorExportToJson exporter = (OperatorExportToJson) factory + .getOperator(Operator.Type.ExportToJson); + + return exporter.execute(spatialReference, geometry); + } + + public static String geometryToGeoJson(Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(geometry); + } + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { + MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); + return geom; + } + + /** + * Exports the specified geometry instance to its GeoJSON representation. + * + * See OperatorExportToGeoJson. + * + * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, + * Geometry geometry) + * + * @param wkid + * The spatial reference Well Known ID to be used for the GeoJSON + * representation. + * @param geometry + * The geometry to be exported to GeoJSON. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. + * + * See OperatorImportFromGeoJson. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(spatialReference, geometry); + } + + /** + * Imports geometry from the ESRI shape file format. + * + * See OperatorImportFromESRIShape. + * + * @param esriShapeBuffer + * The buffer containing geometry in the ESRI shape file format. + * @param geometryType + * The required type of the Geometry to be imported. Use + * Geometry.Type.Unknown if the geometry type needs to be + * determined from the buffer content. + * @return The geometry or null if the buffer contains null shape. + * @throws GeometryException + * when the geometryType is not Geometry.Type.Unknown and the + * buffer contains geometry that cannot be converted to the + * given geometryType. or the buffer is corrupt. Another + * exception possible is IllegalArgumentsException. + */ + public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, + Geometry.Type geometryType) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory + .getOperator(Operator.Type.ImportFromESRIShape); + return op + .execute( + ShapeImportFlags.ShapeImportNonTrusted, + geometryType, + ByteBuffer.wrap(esriShapeBuffer).order( + ByteOrder.LITTLE_ENDIAN)); + } + + /** + * Exports geometry to the ESRI shape file format. + * + * See OperatorExportToESRIShape. + * + * @param geometry + * The geometry to export. (null value is not allowed) + * @return Array containing the exported ESRI shape file. + */ + public static byte[] geometryToEsriShape(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory + .getOperator(Operator.Type.ExportToESRIShape); + return op.execute(0, geometry).array(); + } + + /** + * Imports a geometry from a WKT string. + * + * See OperatorImportFromWkt. + * + * @param wkt The string containing the geometry in WKT format. + * @param importFlags Use the {@link WktImportFlags} interface. + * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. + * @return The geometry. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. + * @throws IllegalArgument exception if an error is found while parsing the WKT string. + */ + public static Geometry geometryFromWkt(String wkt, int importFlags, + Geometry.Type geometryType) { + OperatorImportFromWkt op = (OperatorImportFromWkt) factory + .getOperator(Operator.Type.ImportFromWkt); + return op.execute(importFlags, geometryType, wkt, null); + } + + /** + * Exports a geometry to a string in WKT format. + * + * See OperatorExportToWkt. + * + * @param geometry The geometry to export. (null value is not allowed) + * @param exportFlags Use the {@link WktExportFlags} interface. + * @return A String containing the exported geometry in WKT format. + */ + public static String geometryToWkt(Geometry geometry, int exportFlags) { + OperatorExportToWkt op = (OperatorExportToWkt) factory + .getOperator(Operator.Type.ExportToWkt); + return op.execute(exportFlags, geometry, null); + } + + /** + * Constructs a new geometry by union an array of geometries. All inputs + * must be of the same type of geometries and share one spatial reference. + * + * See OperatorUnion. + * + * @param geometries + * The geometries to union. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry object representing the resultant union. + */ + public static Geometry union(Geometry[] geometries, + SpatialReference spatialReference) { + OperatorUnion op = (OperatorUnion) factory + .getOperator(Operator.Type.Union); + + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometries, spatialReference, + null); + return result.next(); + } + + /** + * Creates the difference of two geometries. The dimension of geometry2 has + * to be equal to or greater than that of geometry1. + * + * See OperatorDifference. + * + * @param geometry1 + * The geometry being subtracted. + * @param substractor + * The geometry object to subtract from. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry of the differences. + */ + public static Geometry difference(Geometry geometry1, Geometry substractor, + SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory + .getOperator(Operator.Type.Difference); + Geometry result = op.execute(geometry1, substractor, spatialReference, + null); + return result; + } + + /** + * Creates the symmetric difference of two geometries. + * + * See OperatorSymmetricDifference. + * + * @param leftGeometry + * is one of the Geometry instances in the XOR operation. + * @param rightGeometry + * is one of the Geometry instances in the XOR operation. + * @param spatialReference + * The spatial reference of the geometries. + * @return Returns the result of the symmetric difference. + */ + public static Geometry symmetricDifference(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference spatialReference) { + OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory + .getOperator(Operator.Type.SymmetricDifference); + Geometry result = op.execute(leftGeometry, rightGeometry, + spatialReference, null); + return result; + } + + /** + * Indicates if two geometries are equal. + * + * See OperatorEquals. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if both geometry objects are equal. + */ + public static boolean equals(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorEquals op = (OperatorEquals) factory + .getOperator(Operator.Type.Equals); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * See OperatorDisjoint. + * + */ + public static boolean disjoint(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDisjoint op = (OperatorDisjoint) factory + .getOperator(Operator.Type.Disjoint); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Constructs the set-theoretic intersection between an array of geometries + * and another geometry. + * + * See OperatorIntersection (also for dimension specific intersection). + * + * @param inputGeometries + * An array of geometry objects. + * @param geometry + * The geometry object. + * @return Any array of geometry objects showing the intersection. + */ + static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( + geometry); + GeometryCursor result = op.execute(inputGeometriesCursor, + intersectorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates a geometry through intersection between two geometries. + * + * See OperatorIntersection. + * + * @param geometry1 + * The first geometry. + * @param intersector + * The geometry to intersect the first geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created through intersection. + */ + public static Geometry intersect(Geometry geometry1, Geometry intersector, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + Geometry result = op.execute(geometry1, intersector, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry is within another geometry. + * + * See OperatorWithin. + * + * @param geometry1 + * The base geometry that is tested for within relationship to + * the other geometry. + * @param geometry2 + * The comparison geometry that is tested for the contains + * relationship to the other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if the first geometry is within the other geometry. + */ + public static boolean within(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorWithin op = (OperatorWithin) factory + .getOperator(Operator.Type.Within); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry contains another geometry. + * + * See OperatorContains. + * + * @param geometry1 + * The geometry that is tested for the contains relationship to + * the other geometry.. + * @param geometry2 + * The geometry that is tested for within relationship to the + * other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 contains geometry2. + */ + public static boolean contains(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorContains op = (OperatorContains) factory + .getOperator(Operator.Type.Contains); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry crosses another geometry. + * + * See OperatorCrosses. + * + * @param geometry1 + * The geometry to cross. + * @param geometry2 + * The geometry being crossed. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 crosses geometry2. + */ + public static boolean crosses(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorCrosses op = (OperatorCrosses) factory + .getOperator(Operator.Type.Crosses); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry touches another geometry. + * + * See OperatorTouches. + * + * @param geometry1 + * The geometry to touch. + * @param geometry2 + * The geometry to be touched. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 touches geometry2. + */ + public static boolean touches(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorTouches op = (OperatorTouches) factory + .getOperator(Operator.Type.Touches); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry overlaps another geometry. + * + * See OperatorOverlaps. + * + * @param geometry1 + * The geometry to overlap. + * @param geometry2 + * The geometry to be overlapped. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 overlaps geometry2. + */ + public static boolean overlaps(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorOverlaps op = (OperatorOverlaps) factory + .getOperator(Operator.Type.Overlaps); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if the given relation holds for the two geometries. + * + * See OperatorRelate. + * + * @param geometry1 + * The first geometry for the relation. + * @param geometry2 + * The second geometry for the relation. + * @param spatialReference + * The spatial reference of the geometries. + * @param relation + * The DE-9IM relation. + * @return TRUE if the given relation holds between geometry1 and geometry2. + */ + public static boolean relate(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference, String relation) { + OperatorRelate op = (OperatorRelate) factory + .getOperator(Operator.Type.Relate); + boolean result = op.execute(geometry1, geometry2, spatialReference, + relation, null); + return result; + } + + /** + * Calculates the 2D planar distance between two geometries. + * + * See OperatorDistance. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. This parameter is not + * used and can be null. + * @return The distance between the two geometries. + */ + public static double distance(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDistance op = (OperatorDistance) factory + .getOperator(Operator.Type.Distance); + double result = op.execute(geometry1, geometry2, null); + return result; + } + + /** + * Calculates the clipped geometry from a target geometry using an envelope. + * + * See OperatorClip. + * + * @param geometry + * The geometry to be clipped. + * @param envelope + * The envelope used to clip. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created by clipping. + */ + public static Geometry clip(Geometry geometry, Envelope envelope, + SpatialReference spatialReference) { + OperatorClip op = (OperatorClip) factory + .getOperator(Operator.Type.Clip); + Geometry result = op.execute(geometry, Envelope2D.construct( + envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), + envelope.getYMax()), spatialReference, null); + return result; + } + + /** + * Calculates the cut geometry from a target geometry using a polyline. For + * Polylines, all left cuts will be grouped together in the first Geometry, + * Right cuts and coincident cuts are grouped in the second Geometry, and + * each undefined cut, along with any uncut parts, are output as separate + * Polylines. For Polygons, all left cuts are grouped in the first Polygon, + * all right cuts are in the second Polygon, and each undefined cut, along + * with any left-over parts after cutting, are output as a separate Polygon. + * If there were no cuts then the array will be empty. An undefined cut will + * only be produced if a left cut or right cut was produced, and there was a + * part left over after cutting or a cut is bounded to the left and right of + * the cutter. + * + * See OperatorCut. + * + * @param cuttee + * The geometry to be cut. + * @param cutter + * The polyline to cut the geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return An array of geometries created from cutting. + */ + public static Geometry[] cut(Geometry cuttee, Polyline cutter, + SpatialReference spatialReference) { + if (cuttee == null || cutter == null) + return null; + + OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); + GeometryCursor cursor = op.execute(true, cuttee, cutter, + spatialReference, null); + ArrayList cutsList = new ArrayList(); + + Geometry geometry; + while ((geometry = cursor.next()) != null) { + if (!geometry.isEmpty()) { + cutsList.add(geometry); + } + } + + return cutsList.toArray(new Geometry[0]); + } + /** + * Calculates a buffer polygon for each geometry at each of the + * corresponding specified distances. It is assumed that all geometries have + * the same spatial reference. There is an option to union the + * returned geometries. + * + * See OperatorBuffer. + * + * @param geometries An array of geometries to be buffered. + * @param spatialReference The spatial reference of the geometries. + * @param distances The corresponding distances for the input geometries to be buffered. + * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. + * @return The buffer of the geometries. + */ + public static Polygon[] buffer(Geometry[] geometries, + SpatialReference spatialReference, double[] distances, + boolean toUnionResults) { + // initially assume distances are in unit of spatial reference + double[] bufferDistances = distances; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + + if (toUnionResults) { + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometriesCursor, + spatialReference, bufferDistances, toUnionResults, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add((Polygon) g); + } + Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); + return buffers; + } else { + Polygon[] buffers = new Polygon[geometries.length]; + for (int i = 0; i < geometries.length; i++) { + buffers[i] = (Polygon) op.execute(geometries[i], + spatialReference, bufferDistances[i], null); + } + return buffers; + } + } + + /** + * Calculates a buffer polygon of the geometry as specified by the + * distance input. The buffer is implemented in the xy-plane. + * + * See OperatorBuffer + * + * @param geometry Geometry to be buffered. + * @param spatialReference The spatial reference of the geometry. + * @param distance The specified distance for buffer. Same units as the spatial reference. + * @return The buffer polygon at the specified distances. + */ + public static Polygon buffer(Geometry geometry, + SpatialReference spatialReference, double distance) { + double bufferDistance = distance; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + Geometry result = op.execute(geometry, spatialReference, + bufferDistance, null); + return (Polygon) result; + } + + /** + * Calculates the convex hull geometry. + * + * See OperatorConvexHull. + * + * @param geometry The input geometry. + * @return Returns the convex hull. + * + * For a Point - returns the same point. For an Envelope - + * returns the same envelope. For a MultiPoint - If the point + * count is one, returns the same multipoint. If the point count + * is two, returns a polyline of the points. Otherwise computes + * and returns the convex hull polygon. For a Segment - returns a + * polyline consisting of the segment. For a Polyline - If + * consists of only one segment, returns the same polyline. + * Otherwise computes and returns the convex hull polygon. For a + * Polygon - If more than one path, or if the path isn't already + * convex, computes and returns the convex hull polygon. + * Otherwise returns the same polygon. + */ + public static Geometry convexHull(Geometry geometry) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + return op.execute(geometry, null); + } + + /** + * Calculates the convex hull. + * + * See OperatorConvexHull + * + * @param geometries + * The input geometry array. + * @param b_merge + * Put true if you want the convex hull of all the geometries in + * the array combined. Put false if you want the convex hull of + * each geometry in the array individually. + * @return Returns an array of convex hulls. If b_merge is true, the result + * will be a one element array consisting of the merged convex hull. + */ + public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( + geometries); + GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = cursor.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] output = new Geometry[resultGeoms.size()]; + + for (int i = 0; i < resultGeoms.size(); i++) + output[i] = resultGeoms.get(i); + + return output; + } + + /** + * Finds the coordinate of the geometry which is closest to the specified + * point. + * + * See OperatorProximity2D. + * + * @param inputPoint + * The point to find the nearest coordinate in the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest coordinate. + */ + public static Proximity2DResult getNearestCoordinate(Geometry geometry, + Point inputPoint, boolean bTestPolygonInterior) { + + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestCoordinate(geometry, + inputPoint, bTestPolygonInterior); + return result; + } + + /** + * Finds nearest vertex on the geometry which is closed to the specified + * point. + * + * See OperatorProximity2D. + * + * @param inputPoint + * The point to find the nearest vertex of the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest vertex. + */ + public static Proximity2DResult getNearestVertex(Geometry geometry, + Point inputPoint) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestVertex(geometry, + inputPoint); + return result; + } + + /** + * Finds all vertices in the given distance from the specified point, sorted + * from the closest to the furthest. + * + * See OperatorProximity2D. + * + * @param inputPoint + * The point to start from. + * @param geometry + * The geometry to consider. + * @param searchRadius + * The search radius. + * @param maxVertexCountToReturn + * The maximum number number of vertices to return. + * @return Proximity2DResult containing the array of nearest vertices. + */ + public static Proximity2DResult[] getNearestVertices(Geometry geometry, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Proximity2DResult[] results = proximity.getNearestVertices(geometry, + inputPoint, searchRadius, maxVertexCountToReturn); + + return results; + } + + /** + * Performs the simplify operation on the geometry. + * + * See OperatorSimplify and See OperatorSimplifyOGC. + * + * @param geometry + * The geometry to be simplified. + * @param spatialReference + * The spatial reference of the geometry to be simplified. + * @return The simplified geometry. + */ + public static Geometry simplify(Geometry geometry, + SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + Geometry result = op.execute(geometry, spatialReference, false, null); + return result; + } + + /** + * Checks if the Geometry is simple. + * + * See OperatorSimplify. + * + * @param geometry + * The geometry to be checked. + * @param spatialReference + * The spatial reference of the geometry. + * @return TRUE if the geometry is simple. + */ + static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); + return result; + } + + /** + * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's + * surface is approximated by a spheroid. The function returns the shortest distance between two points on the + * WGS84 spheroid. + * @param ptFrom The "from" point: long, lat in degrees. + * @param ptTo The "to" point: long, lat in degrees. + * @return The geodesic distance between two points in meters. + */ + public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index f67a4d1f..8bb8f25c 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -1,520 +1,520 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestCut extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testCut4326() { - SpatialReference sr = SpatialReference.create(4326); - testConsiderTouch1(sr); - testConsiderTouch2(sr); - testPolygon5(sr); - testPolygon7(sr); - testPolygon8(sr); - testPolygon9(sr); - testEngine(sr); - - } - - public static void testConsiderTouch1(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline1 = makePolyline1(); - Polyline cutter1 = makePolylineCutter1(); - - GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(length == 6); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 12); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testConsiderTouch2(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline2 = makePolyline2(); - Polyline cutter2 = makePolylineCutter2(); - - GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(Math.abs(length - 5.74264068) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 6.75); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.5) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.25) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1.41421356) <= 0.001); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon5(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon5 = makePolygon5(); - Polyline cutter5 = makePolygonCutter5(); - - GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 4); - assertTrue(pointCount == 12); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon7(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon7 = makePolygon7(); - Polyline cutter7 = makePolygonCutter7(); - GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 1); - assertTrue(point_count == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 2); - assertTrue(point_count == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon8(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon9(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon9 = makePolygon9(); - Polyline cutter9 = makePolygonCutter9(); - GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testEngine(SpatialReference spatialReference) { - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, - spatialReference); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cuts[0]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cuts[1]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - } - - public static Polyline makePolyline1() { - Polyline poly = new Polyline(); - - poly.startPath(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 0); - poly.lineTo(6, 0); - poly.lineTo(8, 0); - poly.lineTo(10, 0); - poly.lineTo(12, 0); - poly.lineTo(14, 0); - poly.lineTo(16, 0); - poly.lineTo(18, 0); - poly.lineTo(20, 0); - - return poly; - } - - public static Polyline makePolylineCutter1() { - Polyline poly = new Polyline(); - - poly.startPath(1, 0); - poly.lineTo(4, 0); - - poly.startPath(6, -1); - poly.lineTo(6, 1); - - poly.startPath(6, 0); - poly.lineTo(8, 0); - - poly.startPath(9, -1); - poly.lineTo(9, 1); - - poly.startPath(10, 0); - poly.lineTo(12, 0); - - poly.startPath(12, 1); - poly.lineTo(12, -1); - - poly.startPath(12, 0); - poly.lineTo(15, 0); - - poly.startPath(15, 1); - poly.lineTo(15, -1); - - poly.startPath(16, 0); - poly.lineTo(16, -1); - poly.lineTo(17, -1); - poly.lineTo(17, 1); - poly.lineTo(17, 0); - poly.lineTo(18, 0); - - poly.startPath(18, 0); - poly.lineTo(18, -1); - - return poly; - } - - public static Polyline makePolyline2() { - Polyline poly = new Polyline(); - - poly.startPath(-2, 0); - poly.lineTo(-1, 0); - poly.lineTo(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 2); - poly.lineTo(8, 2); - poly.lineTo(10, 4); - poly.lineTo(12, 4); - - return poly; - } - - public static Polyline makePolylineCutter2() { - Polyline poly = new Polyline(); - - poly.startPath(-1.5, 0); - poly.lineTo(-.75, 0); - - poly.startPath(-.5, 0); - poly.lineTo(1, 0); - poly.lineTo(1, 2); - poly.lineTo(3, -2); - poly.lineTo(4, 2); - poly.lineTo(5, -2); - poly.lineTo(5, 4); - poly.lineTo(8, 2); - poly.lineTo(6, 0); - poly.lineTo(6, 3); - - poly.startPath(9, 5); - poly.lineTo(9, 2); - poly.lineTo(10, 2); - poly.lineTo(10, 5); - poly.lineTo(10.5, 5); - poly.lineTo(10.5, 3); - - poly.startPath(11, 4); - poly.lineTo(11, 5); - - poly.startPath(12, 5); - poly.lineTo(12, 4); - - return poly; - } - - public static Polygon makePolygon5() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter5() { - Polyline poly = new Polyline(); - - poly.startPath(15, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 30); - poly.lineTo(30, 15); - poly.lineTo(15, 0); - - return poly; - } - - public static Polygon makePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter7() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 20); - poly.lineTo(10, 20); - poly.lineTo(10, 10); - - return poly; - } - - public static Polygon makePolygon8() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter8() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - poly.lineTo(10, 10); - - return poly; - } - - public static Polygon makePolygon9() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(0, 20); - poly.lineTo(0, 30); - poly.lineTo(10, 30); - poly.lineTo(10, 20); - - poly.startPath(0, 40); - poly.lineTo(0, 50); - poly.lineTo(10, 50); - poly.lineTo(10, 40); - - return poly; - } - - public static Polyline makePolygonCutter9() { - Polyline poly = new Polyline(); - - poly.startPath(5, -1); - poly.lineTo(5, 51); - - return poly; - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestCut extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testCut4326() { + SpatialReference sr = SpatialReference.create(4326); + testConsiderTouch1(sr); + testConsiderTouch2(sr); + testPolygon5(sr); + testPolygon7(sr); + testPolygon8(sr); + testPolygon9(sr); + testEngine(sr); + + } + + public static void testConsiderTouch1(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline1 = makePolyline1(); + Polyline cutter1 = makePolylineCutter1(); + + GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(length == 6); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 12); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testConsiderTouch2(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline2 = makePolyline2(); + Polyline cutter2 = makePolylineCutter2(); + + GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(Math.abs(length - 5.74264068) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 6.75); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.5) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.25) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1.41421356) <= 0.001); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon5(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon5 = makePolygon5(); + Polyline cutter5 = makePolygonCutter5(); + + GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 4); + assertTrue(pointCount == 12); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon7(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon7 = makePolygon7(); + Polyline cutter7 = makePolygonCutter7(); + GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 1); + assertTrue(point_count == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 2); + assertTrue(point_count == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon8(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon9(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon9 = makePolygon9(); + Polyline cutter9 = makePolygonCutter9(); + GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testEngine(SpatialReference spatialReference) { + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, + spatialReference); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cuts[0]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cuts[1]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 0); + poly.lineTo(6, 0); + poly.lineTo(8, 0); + poly.lineTo(10, 0); + poly.lineTo(12, 0); + poly.lineTo(14, 0); + poly.lineTo(16, 0); + poly.lineTo(18, 0); + poly.lineTo(20, 0); + + return poly; + } + + public static Polyline makePolylineCutter1() { + Polyline poly = new Polyline(); + + poly.startPath(1, 0); + poly.lineTo(4, 0); + + poly.startPath(6, -1); + poly.lineTo(6, 1); + + poly.startPath(6, 0); + poly.lineTo(8, 0); + + poly.startPath(9, -1); + poly.lineTo(9, 1); + + poly.startPath(10, 0); + poly.lineTo(12, 0); + + poly.startPath(12, 1); + poly.lineTo(12, -1); + + poly.startPath(12, 0); + poly.lineTo(15, 0); + + poly.startPath(15, 1); + poly.lineTo(15, -1); + + poly.startPath(16, 0); + poly.lineTo(16, -1); + poly.lineTo(17, -1); + poly.lineTo(17, 1); + poly.lineTo(17, 0); + poly.lineTo(18, 0); + + poly.startPath(18, 0); + poly.lineTo(18, -1); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + + poly.startPath(-2, 0); + poly.lineTo(-1, 0); + poly.lineTo(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 2); + poly.lineTo(8, 2); + poly.lineTo(10, 4); + poly.lineTo(12, 4); + + return poly; + } + + public static Polyline makePolylineCutter2() { + Polyline poly = new Polyline(); + + poly.startPath(-1.5, 0); + poly.lineTo(-.75, 0); + + poly.startPath(-.5, 0); + poly.lineTo(1, 0); + poly.lineTo(1, 2); + poly.lineTo(3, -2); + poly.lineTo(4, 2); + poly.lineTo(5, -2); + poly.lineTo(5, 4); + poly.lineTo(8, 2); + poly.lineTo(6, 0); + poly.lineTo(6, 3); + + poly.startPath(9, 5); + poly.lineTo(9, 2); + poly.lineTo(10, 2); + poly.lineTo(10, 5); + poly.lineTo(10.5, 5); + poly.lineTo(10.5, 3); + + poly.startPath(11, 4); + poly.lineTo(11, 5); + + poly.startPath(12, 5); + poly.lineTo(12, 4); + + return poly; + } + + public static Polygon makePolygon5() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter5() { + Polyline poly = new Polyline(); + + poly.startPath(15, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 30); + poly.lineTo(30, 15); + poly.lineTo(15, 0); + + return poly; + } + + public static Polygon makePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter7() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 20); + poly.lineTo(10, 20); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon8() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter8() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon9() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(0, 20); + poly.lineTo(0, 30); + poly.lineTo(10, 30); + poly.lineTo(10, 20); + + poly.startPath(0, 40); + poly.lineTo(0, 50); + poly.lineTo(10, 50); + poly.lineTo(10, 40); + + return poly; + } + + public static Polyline makePolygonCutter9() { + Polyline poly = new Polyline(); + + poly.startPath(5, -1); + poly.lineTo(5, 51); + + return poly; + } +} diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index 9c7a915d..5c5b6c41 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -1,64 +1,64 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestFailed extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testCenterXY() { - Envelope env = new Envelope(-130, 30, -70, 50); - assertEquals(-100, env.getCenterX(), 0); - assertEquals(40, env.getCenterY(), 0); - } - - @Test - public void testGeometryOperationSupport() { - Geometry baseGeom = new Point(-130, 10); - Geometry comparisonGeom = new Point(-130, 10); - SpatialReference sr = SpatialReference.create(4326); - - @SuppressWarnings("unused") - Geometry diffGeom = null; - int noException = 1; // no exception - try { - diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); - - } catch (IllegalArgumentException ex) { - noException = 0; - } catch (GeometryException ex) { - noException = 0; - } - assertEquals(noException, 1); - } - - @Test - public void TestIntersection() { - OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersects); - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(20, 0); - - Point point1 = new Point(15, 10); - Point point2 = new Point(2, 10); - Point point3 = new Point(5, 5); - boolean res = op.execute(polygon, point1, null, null); - assertTrue(!res); - res = op.execute(polygon, point2, null, null); - assertTrue(!res); - res = op.execute(polygon, point3, null, null); - assertTrue(res); - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestFailed extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCenterXY() { + Envelope env = new Envelope(-130, 30, -70, 50); + assertEquals(-100, env.getCenterX(), 0); + assertEquals(40, env.getCenterY(), 0); + } + + @Test + public void testGeometryOperationSupport() { + Geometry baseGeom = new Point(-130, 10); + Geometry comparisonGeom = new Point(-130, 10); + SpatialReference sr = SpatialReference.create(4326); + + @SuppressWarnings("unused") + Geometry diffGeom = null; + int noException = 1; // no exception + try { + diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); + + } catch (IllegalArgumentException ex) { + noException = 0; + } catch (GeometryException ex) { + noException = 0; + } + assertEquals(noException, 1); + } + + @Test + public void TestIntersection() { + OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects); + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); + + Point point1 = new Point(15, 10); + Point point2 = new Point(2, 10); + Point point3 = new Point(5, 5); + boolean res = op.execute(polygon, point1, null, null); + assertTrue(!res); + res = op.execute(polygon, point2, null, null); + assertTrue(!res); + res = op.execute(polygon, point3, null, null); + assertTrue(res); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 8777ec19..35c805ea 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -1,104 +1,105 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; - -import org.junit.Test; - -public class TestGeodetic extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testTriangleLength() { - Point pt_0 = new Point(10, 10); - Point pt_1 = new Point(20, 20); - Point pt_2 = new Point(20, 10); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 3744719.4094597572) < 1e-12 * 3744719.4094597572); - } - - @Test - public void testRotationInvariance() { - Point pt_0 = new Point(10, 40); - Point pt_1 = new Point(20, 60); - Point pt_2 = new Point(20, 40); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); - - for (int i = -540; i < 540; i += 5) { - pt_0.setXY(i + 10, 40); - pt_1.setXY(i + 20, 60); - pt_2.setXY(i + 20, 40); - length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); - } - } - - @Test - public void testDistanceFailure() { - { - Point p1 = new Point(-60.668485, -31.996013333333334); - Point p2 = new Point(119.13731666666666, 32.251583333333336); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); - } - - { - Point p1 = new Point(121.27343833333333, 27.467438333333334); - Point p2 = new Point(-58.55804833333333, -27.035613333333334); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); - } - - { - Point p1 = new Point(-53.329865, -36.08110166666667); - Point p2 = new Point(126.52895166666667, 35.97385); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); - } - - { - Point p1 = new Point(-4.7181166667, 36.1160166667); - Point p2 = new Point(175.248925, -35.7606716667); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); - } - } - - @Test - public void testLengthAccurateCR191313() { - /* - * // random_test(); OperatorFactoryLocal engine = - * OperatorFactoryLocal.getInstance(); //TODO: Make this: - * OperatorShapePreservingLength geoLengthOp = - * (OperatorShapePreservingLength) - * factory.getOperator(Operator.Type.ShapePreservingLength); - * SpatialReference spatialRef = SpatialReference.create(102631); - * //[6097817.59407673 - * ,17463475.2931517],[-1168053.34617516,11199801.3734424 - * ]]],"spatialReference":{"wkid":102631} - * - * Polyline polyline = new Polyline(); - * polyline.startPath(6097817.59407673, 17463475.2931517); - * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = - * geoLengthOp.execute(polyline, spatialRef, null); - * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); - */ - } - } +package com.esri.core.geometry; + +import junit.framework.TestCase; + +import org.junit.Test; + +public class TestGeodetic extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testTriangleLength() { + Point pt_0 = new Point(10, 10); + Point pt_1 = new Point(20, 20); + Point pt_2 = new Point(20, 10); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-12 * 3744719.4094597572); + } + + @Test + public void testRotationInvariance() { + Point pt_0 = new Point(10, 40); + Point pt_1 = new Point(20, 60); + Point pt_2 = new Point(20, 40); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); + + for (int i = -540; i < 540; i += 5) { + pt_0.setXY(i + 10, 40); + pt_1.setXY(i + 20, 60); + pt_2.setXY(i + 20, 40); + length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); + } + } + + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + + @Test + public void testLengthAccurateCR191313() { + /* + * // random_test(); OperatorFactoryLocal engine = + * OperatorFactoryLocal.getInstance(); //TODO: Make this: + * OperatorShapePreservingLength geoLengthOp = + * (OperatorShapePreservingLength) + * factory.getOperator(Operator.Type.ShapePreservingLength); + * SpatialReference spatialRef = SpatialReference.create(102631); + * //[6097817.59407673 + * ,17463475.2931517],[-1168053.34617516,11199801.3734424 + * ]]],"spatialReference":{"wkid":102631} + * + * Polyline polyline = new Polyline(); + * polyline.startPath(6097817.59407673, 17463475.2931517); + * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = + * geoLengthOp.execute(polyline, spatialRef, null); + * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); + */ + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 237abb4c..498dab4b 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,565 +1,565 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import junit.framework.TestCase; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; - -public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Test - public void testLocalExport() - throws JsonParseException, IOException { - String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); - //assertTrue(s.contains(".")); - //assertFalse(s.contains(",")); - Polyline line = new Polyline(); - line.startPath(1.1, 2.2); - line.lineTo(2.3, 4.5); - String s1 = OperatorExportToJson.local().execute(null, line); - assertTrue(s.contains(".")); - } - - boolean testPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP - .getSpatialReference().getID() - || pointWebMerc1MP.getSpatialReference().getID() == 3857); - - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - - pointWebMerc1Parser = factory.createParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - if (pointWebMerc1MP.getSpatialReference() != null) { - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - } - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createParser(pointEmptyString); - } - - JsonParser pointWebMerc2Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - if (!checkResultSpatialRef(pointWebMerc2MP, - spatialReferenceWebMerc2.getLatestID(), 0)) { - bAnswer = false; - } - - { - JsonParser pointWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Point p = new Point(); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - } - - { - Point p = new Point(10.0, 20.0, 30.0); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.getX() == 0.0); - assertTrue(pt.getY() == 1.0); - assertTrue(pt.getZ() == 5.0); - assertTrue(pt.getM() == 11.0); - } - - { - String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; - JsonParser parser = factory.createParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.isEmpty()); - SpatialReference spatial_reference = map_pt.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testMultiPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, - multiPoint1); - JsonParser mPointWgs84Parser = factory.createParser(s); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { - bAnswer = false; - } - - } - - { - MultiPoint p = new MultiPoint(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.add(10.0, 20.0, 30.0); - p.add(20.0, 40.0, 60.0); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - { - String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - MapGeometry mp = GeometryEngine.jsonToGeometry(factory - .createParser(points)); - MultiPoint multipoint = (MultiPoint) mp.getGeometry(); - assertTrue(multipoint.getPointCount() == 4); - Point2D point2d; - point2d = multipoint.getXY(0); - assertTrue(point2d.x == 0.0 && point2d.y == 0.0); - point2d = multipoint.getXY(1); - assertTrue(point2d.x == 0.0 && point2d.y == 10.0); - point2d = multipoint.getXY(2); - assertTrue(point2d.x == 10.0 && point2d.y == 10.0); - point2d = multipoint.getXY(3); - assertTrue(point2d.x == 10.0 && point2d.y == 0.0); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); - double z = multipoint.getAttributeAsDbl( - VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 1); - SpatialReference spatial_reference = mp.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolyline() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polyline p = new Polyline(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.startPath(2, 2); - p.lineTo(3, 3); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createParser(paths)); - Polyline p = (Polyline) mapGeometry.getGeometry(); - assertTrue(p.getPathCount() == 2); - @SuppressWarnings("unused") - int count = p.getPathCount(); - assertTrue(p.getPointCount() == 8); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 3); - double length = p.calculateLength2D(); - assertTrue(Math.abs(length - 54.0) <= 0.001); - SpatialReference spatial_reference = mapGeometry - .getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolygon() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polygon p = new Polygon(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.lineTo(4, 4); - p.startPath(2, 2); - p.lineTo(3, 3); - p.lineTo(7, 8); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); - p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); - p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - // Test Import Polygon from Polygon - String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createParser(rings)); - Polygon p = (Polygon) mapGeometry.getGeometry(); - @SuppressWarnings("unused") - double area = p.calculateArea2D(); - @SuppressWarnings("unused") - double length = p.calculateLength2D(); - assertTrue(p.getPathCount() == 4); - int count = p.getPointCount(); - assertTrue(count == 15); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testEnvelope() throws JsonParseException, IOException { - boolean bAnswer = true; - - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - {// export - Envelope e = new Envelope(); - e.addAttribute(VertexDescription.Semantics.Z); - e.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - e); - assertTrue(s - .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - e.setCoords(0, 1, 2, 3); - - Envelope1D z = new Envelope1D(); - Envelope1D m = new Envelope1D(); - z.setCoords(5, 7); - m.setCoords(11, 13); - - e.setInterval(VertexDescription.Semantics.Z, 0, z); - e.setInterval(VertexDescription.Semantics.M, 0, m); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); - assertTrue(s - .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); - Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); - assertTrue(z.vmin == 5.0); - assertTrue(z.vmax == 7.0); - assertTrue(m.vmin == 11.0); - assertTrue(m.vmax == 13.0); - } - - { - String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope2D e = new Envelope2D(); - env.queryEnvelope2D(e); - assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 - && e.ymax == 49.94); - - Envelope1D e1D; - assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); - e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(e1D.vmin == 33 && e1D.vmax == 53); - - assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testCR181369() throws JsonParseException, IOException { - // CR181369 - boolean bAnswer = true; - - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - - String s1 = mapGeom2.getSpatialReference().getText(); - String s2 = mapGeom3.getSpatialReference().getText(); - assertTrue(s1.equals(s2)); - - int id2 = mapGeom2.getSpatialReference().getID(); - int id3 = mapGeom3.getSpatialReference().getID(); - assertTrue(id2 == id3); - if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() - .getID(), 0)) { - bAnswer = false; - } - return bAnswer; - } - - boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, - int expectWki2) { - SpatialReference sr = mapGeometry.getSpatialReference(); - String Wkt = sr.getText(); - int wki1 = sr.getLatestID(); - if (!(wki1 == expectWki1 || wki1 == expectWki2)) - return false; - if (!(Wkt != null && Wkt.length() > 0)) - return false; - SpatialReference sr2 = SpatialReference.create(Wkt); - int wki2 = sr2.getID(); - if (expectWki2 > 0) { - if (!(wki2 == expectWki1 || wki2 == expectWki2)) - return false; - } else { - if (!(wki2 == expectWki1)) - return false; - } - return true; - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; + +public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Test + public void testLocalExport() + throws JsonParseException, IOException { + String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); + //assertTrue(s.contains(".")); + //assertFalse(s.contains(",")); + Polyline line = new Polyline(); + line.startPath(1.1, 2.2); + line.lineTo(2.3, 4.5); + String s1 = OperatorExportToJson.local().execute(null, line); + assertTrue(s.contains(".")); + } + + boolean testPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP + .getSpatialReference().getID() + || pointWebMerc1MP.getSpatialReference().getID() == 3857); + + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + + pointWebMerc1Parser = factory.createParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + if (pointWebMerc1MP.getSpatialReference() != null) { + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + } + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createParser(pointEmptyString); + } + + JsonParser pointWebMerc2Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + if (!checkResultSpatialRef(pointWebMerc2MP, + spatialReferenceWebMerc2.getLatestID(), 0)) { + bAnswer = false; + } + + { + JsonParser pointWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Point p = new Point(); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + } + + { + Point p = new Point(10.0, 20.0, 30.0); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.getX() == 0.0); + assertTrue(pt.getY() == 1.0); + assertTrue(pt.getZ() == 5.0); + assertTrue(pt.getM() == 11.0); + } + + { + String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; + JsonParser parser = factory.createParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.isEmpty()); + SpatialReference spatial_reference = map_pt.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testMultiPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, + multiPoint1); + JsonParser mPointWgs84Parser = factory.createParser(s); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { + bAnswer = false; + } + + } + + { + MultiPoint p = new MultiPoint(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.add(10.0, 20.0, 30.0); + p.add(20.0, 40.0, 60.0); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + { + String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + MapGeometry mp = GeometryEngine.jsonToGeometry(factory + .createParser(points)); + MultiPoint multipoint = (MultiPoint) mp.getGeometry(); + assertTrue(multipoint.getPointCount() == 4); + Point2D point2d; + point2d = multipoint.getXY(0); + assertTrue(point2d.x == 0.0 && point2d.y == 0.0); + point2d = multipoint.getXY(1); + assertTrue(point2d.x == 0.0 && point2d.y == 10.0); + point2d = multipoint.getXY(2); + assertTrue(point2d.x == 10.0 && point2d.y == 10.0); + point2d = multipoint.getXY(3); + assertTrue(point2d.x == 10.0 && point2d.y == 0.0); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + double z = multipoint.getAttributeAsDbl( + VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 1); + SpatialReference spatial_reference = mp.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolyline() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polyline p = new Polyline(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.startPath(2, 2); + p.lineTo(3, 3); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createParser(paths)); + Polyline p = (Polyline) mapGeometry.getGeometry(); + assertTrue(p.getPathCount() == 2); + @SuppressWarnings("unused") + int count = p.getPathCount(); + assertTrue(p.getPointCount() == 8); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 3); + double length = p.calculateLength2D(); + assertTrue(Math.abs(length - 54.0) <= 0.001); + SpatialReference spatial_reference = mapGeometry + .getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolygon() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polygon p = new Polygon(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.lineTo(4, 4); + p.startPath(2, 2); + p.lineTo(3, 3); + p.lineTo(7, 8); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); + p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); + p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + // Test Import Polygon from Polygon + String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createParser(rings)); + Polygon p = (Polygon) mapGeometry.getGeometry(); + @SuppressWarnings("unused") + double area = p.calculateArea2D(); + @SuppressWarnings("unused") + double length = p.calculateLength2D(); + assertTrue(p.getPathCount() == 4); + int count = p.getPointCount(); + assertTrue(count == 15); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testEnvelope() throws JsonParseException, IOException { + boolean bAnswer = true; + + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + {// export + Envelope e = new Envelope(); + e.addAttribute(VertexDescription.Semantics.Z); + e.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + e); + assertTrue(s + .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + e.setCoords(0, 1, 2, 3); + + Envelope1D z = new Envelope1D(); + Envelope1D m = new Envelope1D(); + z.setCoords(5, 7); + m.setCoords(11, 13); + + e.setInterval(VertexDescription.Semantics.Z, 0, z); + e.setInterval(VertexDescription.Semantics.M, 0, m); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); + assertTrue(s + .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); + Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); + assertTrue(z.vmin == 5.0); + assertTrue(z.vmax == 7.0); + assertTrue(m.vmin == 11.0); + assertTrue(m.vmax == 13.0); + } + + { + String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope2D e = new Envelope2D(); + env.queryEnvelope2D(e); + assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 + && e.ymax == 49.94); + + Envelope1D e1D; + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(e1D.vmin == 33 && e1D.vmax == 53); + + assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testCR181369() throws JsonParseException, IOException { + // CR181369 + boolean bAnswer = true; + + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + + String s1 = mapGeom2.getSpatialReference().getText(); + String s2 = mapGeom3.getSpatialReference().getText(); + assertTrue(s1.equals(s2)); + + int id2 = mapGeom2.getSpatialReference().getID(); + int id3 = mapGeom3.getSpatialReference().getID(); + assertTrue(id2 == id3); + if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() + .getID(), 0)) { + bAnswer = false; + } + return bAnswer; + } + + boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, + int expectWki2) { + SpatialReference sr = mapGeometry.getSpatialReference(); + String Wkt = sr.getText(); + int wki1 = sr.getLatestID(); + if (!(wki1 == expectWki1 || wki1 == expectWki2)) + return false; + if (!(Wkt != null && Wkt.length() > 0)) + return false; + SpatialReference sr2 = SpatialReference.create(Wkt); + int wki2 = sr2.getID(); + if (expectWki2 > 0) { + if (!(wki2 == expectWki1 || wki2 == expectWki2)) + return false; + } else { + if (!(wki2 == expectWki1)) + return false; + } + return true; + } +} diff --git a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java index 571a103e..a996575f 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java +++ b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java @@ -1,47 +1,48 @@ -package com.esri.core.geometry; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestJSonGeometry extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testGetSpatialReferenceFor4326() { - String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"; - - // 4326 GCS_WGS_1984 - SpatialReference sr = SpatialReference.create(completeStr); - assertNotNull(sr); - } - } - -final class HashMapClassForTesting { - static Map SR_WKI_WKTs = new HashMap() { - /** - * added to get rid of warning - */ - private static final long serialVersionUID = 8630934425353750539L; - - { - put(4035, - "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"); - } - }; -} +package com.esri.core.geometry; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestJSonGeometry extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testGetSpatialReferenceFor4326() { + String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"; + + // 4326 GCS_WGS_1984 + SpatialReference sr = SpatialReference.create(completeStr); + assertNotNull(sr); + } + +} + +final class HashMapClassForTesting { + static Map SR_WKI_WKTs = new HashMap() { + /** + * added to get rid of warning + */ + private static final long serialVersionUID = 8630934425353750539L; + + { + put(4035, + "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"); + } + }; +} diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 8abd75cf..8b036785 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,124 +1,124 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import junit.framework.TestCase; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; - -public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { - JsonFactory factory = new JsonFactory(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, - IOException { - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " - + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " - + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " - + "\"spatialReference\" : {\"wkt\" : \"\"}}"; - JsonParser jsonParserPg = factory.createParser(jsonStringPg); - jsonParserPg.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testOnlyWKI() throws JsonParseException, IOException { - String jsonStringSR = "{\"wkid\" : 4326}"; - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - jsonParserSR.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - try { - String jSonStr = GeometryEngine.geometryToJson(4326, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - assertEquals(Geometry.Type.Polygon, gm.getType()); - - Polygon pgNew = (Polygon) gm; - - assertEquals(pgNew.getPathCount(), pg.getPathCount()); - assertEquals(pgNew.getPointCount(), pg.getPointCount()); - assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), - 0.000000001); - - assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), - 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { + JsonFactory factory = new JsonFactory(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + IOException { + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " + + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + + "\"spatialReference\" : {\"wkt\" : \"\"}}"; + JsonParser jsonParserPg = factory.createParser(jsonStringPg); + jsonParserPg.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testOnlyWKI() throws JsonParseException, IOException { + String jsonStringSR = "{\"wkid\" : 4326}"; + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + jsonParserSR.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + try { + String jSonStr = GeometryEngine.geometryToJson(4326, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + assertEquals(Geometry.Type.Polygon, gm.getType()); + + Polygon pgNew = (Polygon) gm; + + assertEquals(pgNew.getPathCount(), pg.getPathCount()); + assertEquals(pgNew.getPointCount(), pg.getPointCount()); + assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), + 0.000000001); + + assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), + 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } +} diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index d6593115..9f2d887f 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -1,664 +1,664 @@ -package com.esri.core.geometry; - -import java.util.Hashtable; -import java.io.IOException; -import java.util.Map; -import junit.framework.TestCase; -import org.junit.Assert; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; - -public class TestJsonParser extends TestCase { - - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } - - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory - .createJsonParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory - .createJsonParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText() - .equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 - .getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory - .createJsonParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory - .createJsonParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory - .createJsonParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory - .createJsonParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine - .jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06298);// -97.06153, 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) - .getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06153);// -97.06153, 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - Assert.assertTrue("Should not throw for invalid wkid", false); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine - .geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), - pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), - pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), - pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), - pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), - pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), - pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), - pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), - pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out - .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) { - System.out.println("No spatial reference"); - } else { - System.out.println("wkid: " - + geom.getSpatialReference().getID()); - } - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() - + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() - + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " - + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - //System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - //System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson( - SpatialReference.create(3857), geom);// Test WKID == -1 - String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createJsonParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } -} +package com.esri.core.geometry; + +import java.util.Hashtable; +import java.io.IOException; +import java.util.Map; +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class TestJsonParser extends TestCase { + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP + .getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory + .createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory + .createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText() + .equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 + .getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory + .createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory + .createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory + .createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory + .createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine + .jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06298);// -97.06153, 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) + .getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06153);// -97.06153, 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + Assert.assertTrue("Should not throw for invalid wkid", false); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine + .geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), + pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), + pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), + pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), + pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), + pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), + pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), + pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), + pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out + .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) { + System.out.println("No spatial reference"); + } else { + System.out.println("wkid: " + + geom.getSpatialReference().getID()); + } + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + //System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + //System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson( + SpatialReference.create(3857), geom);// Test WKID == -1 + String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 4d736a8c..269b0879 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -1,380 +1,380 @@ -package com.esri.core.geometry; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestSerialization extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testSerializePoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Point pt = new Point(10, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Point serialization failure"); - - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Point pt = new Point(10, 40, 2); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Point serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); - } catch (Exception ex) { - fail("Point serialization failure"); - } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); - } catch (Exception ex) { - fail("Point serialization failure"); - } - - } - - @Test - public void testSerializePolygon() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - pt = (Polygon) GeometryEngine.simplify(pt, null); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Polygon pt = new Polygon(); - //pt.startPath(10, 10); - //pt.lineTo(100, 100); - //pt.lineTo(200, 100); - //pt = (Polygon)GeometryEngine.simplify(pt, null); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Polygon serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - } - - @Test - public void testSerializePolyline() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polyline pt = new Polyline(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Polyline pt = new Polyline(); - //pt.startPath(10, 10); - //pt.lineTo(100, 100); - //pt.lineTo(200, 100); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Polyline serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - } - - @Test - public void testSerializeEnvelope() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope pt = new Envelope(10, 10, 400, 300); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Envelope pt = new Envelope(10, 10, 400, 300); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Envelope serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - } - - @Test - public void testSerializeMultiPoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - MultiPoint pt = new MultiPoint(); - pt.add(10, 30); - pt.add(120, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //MultiPoint pt = new MultiPoint(); - //pt.add(10, 30); - //pt.add(120, 40); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("MultiPoint serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - } - - @Test - public void testSerializeLine() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Line pt = new Line(); - pt.setStart(new Point(10, 30)); - pt.setEnd(new Point(120, 40)); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Line ptRes = (Line) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - // fail("Line serialization failure"); - assertEquals(ex.getMessage(), "Cannot serialize this geometry"); - } - } - - @Test - public void testSerializeSR() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - SpatialReference sr = SpatialReference.create(102100); - oo.writeObject(sr); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - SpatialReference ptRes = (SpatialReference) ii.readObject(); - assertTrue(ptRes.equals(sr)); - } catch (Exception ex) { - fail("Spatial Reference serialization failure"); - } - } - - @Test - public void testSerializeEnvelope2D() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); - oo.writeObject(env); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope2D envRes = (Envelope2D)ii.readObject(); - assertTrue(envRes.equals(env)); - } catch (Exception ex) { - fail("Envelope2D serialization failure"); - } - -// try -// { -// FileOutputStream streamOut = new FileOutputStream( -// "c:/temp/savedEnvelope2D.txt"); -// ObjectOutputStream oo = new ObjectOutputStream(streamOut); -// Envelope2D e = new Envelope2D(177.123, 188.234, 999.122, 888.999); -// oo.writeObject(e); -// } -// catch(Exception ex) -// { -// fail("Envelope2D serialization failure"); -// } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope2D.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope2D e = (Envelope2D) ii - .readObject(); - assertTrue(e != null); - assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); - } catch (Exception ex) { - fail("Envelope2D serialization failure"); - } - } - -} +package com.esri.core.geometry; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestSerialization extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSerializePoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Point pt = new Point(10, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Point serialization failure"); + + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Point pt = new Point(10, 40, 2); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Point serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); + } catch (Exception ex) { + fail("Point serialization failure"); + } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); + } catch (Exception ex) { + fail("Point serialization failure"); + } + + } + + @Test + public void testSerializePolygon() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + pt = (Polygon) GeometryEngine.simplify(pt, null); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polygon pt = new Polygon(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //pt = (Polygon)GeometryEngine.simplify(pt, null); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polygon serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + } + + @Test + public void testSerializePolyline() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polyline pt = new Polyline(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polyline pt = new Polyline(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polyline serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + } + + @Test + public void testSerializeEnvelope() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope pt = new Envelope(10, 10, 400, 300); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Envelope pt = new Envelope(10, 10, 400, 300); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Envelope serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + } + + @Test + public void testSerializeMultiPoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + MultiPoint pt = new MultiPoint(); + pt.add(10, 30); + pt.add(120, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //MultiPoint pt = new MultiPoint(); + //pt.add(10, 30); + //pt.add(120, 40); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("MultiPoint serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + } + + @Test + public void testSerializeLine() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Line pt = new Line(); + pt.setStart(new Point(10, 30)); + pt.setEnd(new Point(120, 40)); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Line ptRes = (Line) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + // fail("Line serialization failure"); + assertEquals(ex.getMessage(), "Cannot serialize this geometry"); + } + } + + @Test + public void testSerializeSR() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + SpatialReference sr = SpatialReference.create(102100); + oo.writeObject(sr); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + SpatialReference ptRes = (SpatialReference) ii.readObject(); + assertTrue(ptRes.equals(sr)); + } catch (Exception ex) { + fail("Spatial Reference serialization failure"); + } + } + + @Test + public void testSerializeEnvelope2D() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); + oo.writeObject(env); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope2D envRes = (Envelope2D)ii.readObject(); + assertTrue(envRes.equals(env)); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + +// try +// { +// FileOutputStream streamOut = new FileOutputStream( +// "c:/temp/savedEnvelope2D.txt"); +// ObjectOutputStream oo = new ObjectOutputStream(streamOut); +// Envelope2D e = new Envelope2D(177.123, 188.234, 999.122, 888.999); +// oo.writeObject(e); +// } +// catch(Exception ex) +// { +// fail("Envelope2D serialization failure"); +// } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope2D.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope2D e = (Envelope2D) ii + .readObject(); + assertTrue(e != null); + assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index b7380851..47a741c1 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -1,1344 +1,1344 @@ -package com.esri.core.geometry; - -//import java.io.FileOutputStream; -//import java.io.PrintStream; -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Random; -import java.io.IOException; - -import junit.framework.TestCase; - -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; - -public class TestSimplify extends TestCase { - OperatorFactoryLocal factory = null; - OperatorSimplify simplifyOp = null; - OperatorSimplifyOGC simplifyOpOGC = null; - SpatialReference sr102100 = null; - SpatialReference sr4326 = null; - SpatialReference sr3857 = null; - - @Override - protected void setUp() throws Exception { - super.setUp(); - factory = OperatorFactoryLocal.getInstance(); - simplifyOp = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - simplifyOpOGC = (OperatorSimplifyOGC) factory - .getOperator(Operator.Type.SimplifyOGC); - sr102100 = SpatialReference.create(102100); - sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); - sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, - // Code, GCS_WGS_1984)); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public Polygon makeNonSimplePolygon2() { - //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); - //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); - - - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - return poly; - }// done - - /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- - * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | - * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | - * -------<------- | | (3) | -------------<---------------<--------------- - * -->-- - */ - - // Bowtie case with vertices at intersection - - public Polygon makeNonSimplePolygon5() { - Polygon poly = new Polygon(); - poly.startPath(10, 0); - poly.lineTo(0, 0); - poly.lineTo(5, 5); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(5, 5); - - return poly; - }// done - - @Test - public void test0() { - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Poly() {// simple - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Polygon_Spike1() {// non-simple (spike) - Polygon poly1 = new Polygon(); - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike2() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // rectangle with a spike - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike3() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - // touch uncracked - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(0, 50); - poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(-100, 0); - // poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing1() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary1() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 0); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary2() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 5); - poly.lineTo(10, 6); - poly.lineTo(20, 6); - poly.lineTo(20, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void testPolygon() { - Polygon nonSimplePolygon = makeNonSimplePolygon(); - Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, - sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, - null, null); - assertTrue(res); - - @SuppressWarnings("unused") - int partCount = simplePolygon.getPathCount(); - // assertTrue(partCount == 2); - - double area = simplePolygon.calculateRingArea2D(0); - assertTrue(Math.abs(area - 300) <= 0.0001); - - area = simplePolygon.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-25.0)) <= 0.0001); - }// done - - @Test - public void testPolygon2() { - Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); - double area = nonSimplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - 1.0) <= 0.0001); - - Polygon simplePolygon2 = (Polygon) simplifyOp.execute( - nonSimplePolygon2, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, - true, null, null); - assertTrue(res); - - area = simplePolygon2.calculateRingArea2D(0); - assertTrue(Math.abs(area - 225) <= 0.0001); - - area = simplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-1.0)) <= 0.0001); - }// done - - @Test - public void testPolygon3() { - Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); - Polygon simplePolygon3 = (Polygon) simplifyOp.execute( - nonSimplePolygon3, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, - true, null, null); - assertTrue(res); - - double area = simplePolygon3.calculateRingArea2D(0); - assertTrue(Math.abs(area - 875) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(2); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(3); - assertTrue(Math.abs(area - 25) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(4); - assertTrue(Math.abs(area - 25) <= 0.0001); - }// done - - @Test - public void testPolyline() { - Polyline nonSimplePolyline = makeNonSimplePolyline(); - Polyline simplePolyline = (Polyline) simplifyOp.execute( - nonSimplePolyline, sr3857, false, null); - - int segmentCount = simplePolyline.getSegmentCount(); - assertTrue(segmentCount == 4); - }// done - - @Test - public void testPolygon4() { - Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); - Polygon simplePolygon4 = (Polygon) simplifyOp.execute( - nonSimplePolygon4, sr3857, false, null); - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, - true, null, null); - assertTrue(res); - - assertTrue(simplePolygon4.getPointCount() == 5); - Point point = nonSimplePolygon4.getPoint(0); - assertTrue(point.getX() == 0.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(1); - assertTrue(point.getX() == 0.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(2); - assertTrue(point.getX() == 10.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(3); - assertTrue(point.getX() == 10.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(4); - assertTrue(point.getX() == 5.0 && point.getY() == 0.0); - }// done - - @Test - public void testPolygon5() { - Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); - Polygon simplePolygon5 = (Polygon) simplifyOp.execute( - nonSimplePolygon5, sr3857, false, null); - assertTrue(simplePolygon5 != null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, - true, null, null); - assertTrue(res); - - int pointCount = simplePolygon5.getPointCount(); - assertTrue(pointCount == 6); - - double area = simplePolygon5.calculateArea2D(); - assertTrue(Math.abs(area - 50.0) <= 0.001); - - }// done - - @Test - public void testPolygon6() { - Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); - Polygon simplePolygon6 = (Polygon) simplifyOp.execute( - nonSimplePolygon6, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, - true, null, null); - assertTrue(res); - } - - @Test - public void testPolygon7() { - Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); - Polygon simplePolygon7 = (Polygon) simplifyOp.execute( - nonSimplePolygon7, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, - true, null, null); - assertTrue(res); - } - - public Polygon makeNonSimplePolygon() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - return poly; - }// done - - /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- - * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | - * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | - * -------<------- | | (3) | -------------<---------------<--------------- - * -->-- - */ - - public Polygon makeNonSimplePolygon3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 25); - poly.lineTo(35, 25); - poly.lineTo(35, 0); - - poly.startPath(5, 5); - poly.lineTo(5, 15); - poly.lineTo(10, 15); - poly.lineTo(10, 5); - - poly.startPath(40, 0); - poly.lineTo(45, 0); - poly.lineTo(45, 5); - poly.lineTo(40, 5); - - poly.startPath(20, 10); - poly.lineTo(25, 10); - poly.lineTo(25, 15); - poly.lineTo(20, 15); - - poly.startPath(15, 5); - poly.lineTo(15, 20); - poly.lineTo(30, 20); - poly.lineTo(30, 5); - - return poly; - }// done - - public Polygon makeNonSimplePolygon4() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - poly.lineTo(5, 0); - poly.lineTo(5, 5); - poly.lineTo(5, 0); - - return poly; - }// done - - public Polygon makeNonSimplePolygon6() { - Polygon poly = new Polygon(); - poly.startPath(35.34407570857744, 54.00551247713412); - poly.lineTo(41.07663499357954, 20.0); - poly.lineTo(40.66372033705177, 26.217432321849017); - - poly.startPath(42.81936574509338, 20.0); - poly.lineTo(43.58226670584747, 20.0); - poly.lineTo(39.29611825817084, 22.64634933678729); - poly.lineTo(44.369873312241346, 25.81893670527215); - poly.lineTo(42.68845660737179, 20.0); - poly.lineTo(38.569549792944244, 56.47456192829393); - poly.lineTo(42.79274114188401, 45.45117792578003); - poly.lineTo(41.09512147544657, 70.0); - - return poly; - } - - public Polygon makeNonSimplePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(41.987895433319686, 53.75822619011542); - poly.lineTo(41.98789542535497, 53.75822618803151); - poly.lineTo(40.15120412113667, 68.12604154722113); - poly.lineTo(37.72272697311022, 67.92767094118877); - poly.lineTo(37.147347454283086, 49.497473094145505); - poly.lineTo(38.636627026664385, 51.036687142232736); - - poly.startPath(39.00920080789793, 62.063425518369016); - poly.lineTo(38.604912643136885, 70.0); - poly.lineTo(40.71826863485308, 43.60337143116787); - poly.lineTo(35.34407570857744, 54.005512477134126); - poly.lineTo(39.29611825817084, 22.64634933678729); - - return poly; - } - - public Polyline makeNonSimplePolyline() { - // This polyline has a short segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - poly.lineTo(10, 10); - poly.lineTo(10, 5); - poly.lineTo(-5, 5); - - return poly; - }// done - - @Test - public void testIsSimpleBasicsPoint() { - boolean result; - // point is always simple - Point pt = new Point(); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(0, 0); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(100000, 10000); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleBasicsEnvelope() { - // Envelope is simple, when it's width and height are not degenerate - Envelope env = new Envelope(); - boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, - null); // Empty is simple - assertTrue(result); - env.setCoords(0, 0, 10, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver but still simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver and not simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(!result); - }// done - - @Test - public void testIsSimpleBasicsLine() { - Line line = new Line(); - boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, - null, null); - assertTrue(!result); - - line.setStart(new Point(0, 0)); - // line.setEndXY(0, 0); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(!result); - line.setEnd(new Point(1, 0)); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint1() { - MultiPoint mp = new MultiPoint(); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint2FarApart() { - // Two point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(20, 10); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(mp.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointCoincident() { - // Two point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 1); - }// done - - @Test - public void testMultiPointSR4326_CR184439() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorSimplify simpOp = (OperatorSimplify) engine - .getOperator(Operator.Type.Simplify); - NonSimpleResult nonSimpResult = new NonSimpleResult(); - nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0, 0); - multiPoint.add(0, 1); - multiPoint.add(0, 0); - Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, - SpatialReference.create(4326), true, nonSimpResult, null); - assertFalse(multiPointIsSimple); - assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); - assertTrue(nonSimpResult.m_vertexIndex1 == 0); - assertTrue(nonSimpResult.m_vertexIndex2 == 2); - } - - @Test - public void testIsSimpleMultiPointCloserThanTolerance() { - // Two point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - MultiPoint mpS; - mp.add(100, 100); - mp.add(100, 100 + sr4326.getTolerance() * .5); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointFarApart2() { - // 5 point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(101, 101); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimpleMultiPoint_coincident2() { - // 5 point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100); - mp.add(11, 1); - mp.add(11, 14); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 4); - assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); - assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); - assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); - }// done - - @Test - public void testIsSimpleMultiPointCloserThanTolerance2() { - // 5 point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100 + sr4326.getTolerance() / 2); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimplePolyline() { - Polyline poly = new Polyline(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolylineFarApart() { - // Two point test: far apart - Polyline poly = new Polyline(); - poly.startPath(20, 10); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCoincident() { - // Two point test: coincident - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCloserThanTolerance() { - // Two point test: closer than tolerance - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap0() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineSelfIntersect() { - // 4 point test: far apart, self intersecting - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateSegment() { - // 4 point test: degenerate segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - { - Polyline other = new Polyline(); - other.startPath(0, 0); - other.lineTo(100, 100); - other.lineTo(100, 0); - other.equals(poly); - } - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartIntersect() { - // 4 point 2 parts test: far apart, intersecting parts - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 0); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartOverlap2() { - // 4 point 2 parts test: far apart, overlapping parts. second part - // starts where first one ends - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 100); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateVertical() { - // 3 point test: degenerate vertical line - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(new Point(100, 100)); - poly.lineTo(new Point(100, 100)); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.getPointCount() == 2); - } - - @Test - public void testIsSimplePolylineEmptyPath() { - // TODO: any way to test this? - // Empty path - // Polyline poly = new Polyline(); - // assertTrue(poly.isEmpty()); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.isEmpty()); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, null); - // assertTrue(result); - } - - @Test - public void testIsSimplePolylineSinglePointInPath() { - // Single point in path - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.removePoint(0, 1); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.isEmpty()); - } - - @Test - public void testIsSimplePolygon() { - Polygon poly = new Polygon(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolygonEmptyPath() { - // TODO: - // Empty path - // Polygon poly = new Polygon(); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.getPathCount() == 1); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, - // null); - // assertTrue(result); - // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, - // sr4326, false, null), sr4326, false, null, null); - // assertTrue(result);// empty is simple - // assertTrue(poly.getPathCount() == 1); - } - - @Test - public void testIsSimplePolygonIncomplete1() { - // Incomplete polygon 1 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonIncomplete2() { - // Incomplete polygon 2 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonDegenerateTriangle() { - // Degenerate triangle (self overlap) - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersect() { - // Self intersection - cracking is needed - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole() { - // Rectangle and rectangular hole that has one segment overlapping - // with the with the exterior ring. Cracking is needed. - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole2() { - // Rectangle and rectangular hole - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersectAtVertex() { - // Self intersection at vertex - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygon_2EdgesTouchAtVertex() { - // No self-intersection, but more than two edges touch at the same - // vertex. Simple for ArcGIS, not simple for OGC - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(0, 100); - poly.lineTo(100, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygonTriangle() { - // Triangle - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangle() { - // Rectangle - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHoleWrongDirection() { - // Rectangle and rectangular hole that has wrong direction - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygon_2RectanglesSideBySide() { - // Two rectangles side by side, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(220, -50, 300, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleOneBelow() { - // Two rectangles one below another, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(50, 50, 100, 100), false); - poly.addEnvelope(new Envelope(50, 200, 100, 250), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testisSimpleOGC() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, - null); - assertTrue(result); - - poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(10, 0); - NonSimpleResult nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); - - MultiPoint mp = new MultiPoint(); - mp.add(0, 0); - mp.add(10, 0); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); - assertTrue(result); - - mp = new MultiPoint(); - mp.add(10, 0); - mp.add(10, 0); - nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); - } - - @Test - public void testPolylineIsSimpleForOGC() { - OperatorImportFromJson importerJson = (OperatorImportFromJson) factory - .getOperator(Operator.Type.ImportFromJson); - OperatorSimplify simplify = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - - { - String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - { - String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self - // intersection - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed - // with - // self - // tangent - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two - // paths - // connected - // at - // a - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two - // closed - // rings - // touch - // at - // one - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two - // lines - // intersect - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two - // paths - // share - // mid - // point. - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - } - - @Test - public void testFillRule() { - //self intersecting star shape - MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); - Polygon poly = (Polygon)mg.getGeometry(); - assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); - poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); - assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); - Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); - assertTrue(((Polygon)simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); - //solid start without holes: - MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); - boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); - assertTrue(equals); - } - -} +package com.esri.core.geometry; + +//import java.io.FileOutputStream; +//import java.io.PrintStream; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Random; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; + +public class TestSimplify extends TestCase { + OperatorFactoryLocal factory = null; + OperatorSimplify simplifyOp = null; + OperatorSimplifyOGC simplifyOpOGC = null; + SpatialReference sr102100 = null; + SpatialReference sr4326 = null; + SpatialReference sr3857 = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + factory = OperatorFactoryLocal.getInstance(); + simplifyOp = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplifyOpOGC = (OperatorSimplifyOGC) factory + .getOperator(Operator.Type.SimplifyOGC); + sr102100 = SpatialReference.create(102100); + sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); + sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, + // Code, GCS_WGS_1984)); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public Polygon makeNonSimplePolygon2() { + //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); + //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); + + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + // Bowtie case with vertices at intersection + + public Polygon makeNonSimplePolygon5() { + Polygon poly = new Polygon(); + poly.startPath(10, 0); + poly.lineTo(0, 0); + poly.lineTo(5, 5); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(5, 5); + + return poly; + }// done + + @Test + public void test0() { + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Poly() {// simple + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Polygon_Spike1() {// non-simple (spike) + Polygon poly1 = new Polygon(); + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike2() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // rectangle with a spike + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike3() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + // touch uncracked + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(0, 50); + poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(-100, 0); + // poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary1() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 0); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary2() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 5); + poly.lineTo(10, 6); + poly.lineTo(20, 6); + poly.lineTo(20, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void testPolygon() { + Polygon nonSimplePolygon = makeNonSimplePolygon(); + Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, + sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, + null, null); + assertTrue(res); + + @SuppressWarnings("unused") + int partCount = simplePolygon.getPathCount(); + // assertTrue(partCount == 2); + + double area = simplePolygon.calculateRingArea2D(0); + assertTrue(Math.abs(area - 300) <= 0.0001); + + area = simplePolygon.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-25.0)) <= 0.0001); + }// done + + @Test + public void testPolygon2() { + Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); + double area = nonSimplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - 1.0) <= 0.0001); + + Polygon simplePolygon2 = (Polygon) simplifyOp.execute( + nonSimplePolygon2, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, + true, null, null); + assertTrue(res); + + area = simplePolygon2.calculateRingArea2D(0); + assertTrue(Math.abs(area - 225) <= 0.0001); + + area = simplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-1.0)) <= 0.0001); + }// done + + @Test + public void testPolygon3() { + Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); + Polygon simplePolygon3 = (Polygon) simplifyOp.execute( + nonSimplePolygon3, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, + true, null, null); + assertTrue(res); + + double area = simplePolygon3.calculateRingArea2D(0); + assertTrue(Math.abs(area - 875) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(2); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(3); + assertTrue(Math.abs(area - 25) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(4); + assertTrue(Math.abs(area - 25) <= 0.0001); + }// done + + @Test + public void testPolyline() { + Polyline nonSimplePolyline = makeNonSimplePolyline(); + Polyline simplePolyline = (Polyline) simplifyOp.execute( + nonSimplePolyline, sr3857, false, null); + + int segmentCount = simplePolyline.getSegmentCount(); + assertTrue(segmentCount == 4); + }// done + + @Test + public void testPolygon4() { + Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); + Polygon simplePolygon4 = (Polygon) simplifyOp.execute( + nonSimplePolygon4, sr3857, false, null); + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, + true, null, null); + assertTrue(res); + + assertTrue(simplePolygon4.getPointCount() == 5); + Point point = nonSimplePolygon4.getPoint(0); + assertTrue(point.getX() == 0.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(1); + assertTrue(point.getX() == 0.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(2); + assertTrue(point.getX() == 10.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(3); + assertTrue(point.getX() == 10.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(4); + assertTrue(point.getX() == 5.0 && point.getY() == 0.0); + }// done + + @Test + public void testPolygon5() { + Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); + Polygon simplePolygon5 = (Polygon) simplifyOp.execute( + nonSimplePolygon5, sr3857, false, null); + assertTrue(simplePolygon5 != null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, + true, null, null); + assertTrue(res); + + int pointCount = simplePolygon5.getPointCount(); + assertTrue(pointCount == 6); + + double area = simplePolygon5.calculateArea2D(); + assertTrue(Math.abs(area - 50.0) <= 0.001); + + }// done + + @Test + public void testPolygon6() { + Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); + Polygon simplePolygon6 = (Polygon) simplifyOp.execute( + nonSimplePolygon6, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, + true, null, null); + assertTrue(res); + } + + @Test + public void testPolygon7() { + Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); + Polygon simplePolygon7 = (Polygon) simplifyOp.execute( + nonSimplePolygon7, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, + true, null, null); + assertTrue(res); + } + + public Polygon makeNonSimplePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + public Polygon makeNonSimplePolygon3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 25); + poly.lineTo(35, 25); + poly.lineTo(35, 0); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(10, 15); + poly.lineTo(10, 5); + + poly.startPath(40, 0); + poly.lineTo(45, 0); + poly.lineTo(45, 5); + poly.lineTo(40, 5); + + poly.startPath(20, 10); + poly.lineTo(25, 10); + poly.lineTo(25, 15); + poly.lineTo(20, 15); + + poly.startPath(15, 5); + poly.lineTo(15, 20); + poly.lineTo(30, 20); + poly.lineTo(30, 5); + + return poly; + }// done + + public Polygon makeNonSimplePolygon4() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + poly.lineTo(5, 0); + poly.lineTo(5, 5); + poly.lineTo(5, 0); + + return poly; + }// done + + public Polygon makeNonSimplePolygon6() { + Polygon poly = new Polygon(); + poly.startPath(35.34407570857744, 54.00551247713412); + poly.lineTo(41.07663499357954, 20.0); + poly.lineTo(40.66372033705177, 26.217432321849017); + + poly.startPath(42.81936574509338, 20.0); + poly.lineTo(43.58226670584747, 20.0); + poly.lineTo(39.29611825817084, 22.64634933678729); + poly.lineTo(44.369873312241346, 25.81893670527215); + poly.lineTo(42.68845660737179, 20.0); + poly.lineTo(38.569549792944244, 56.47456192829393); + poly.lineTo(42.79274114188401, 45.45117792578003); + poly.lineTo(41.09512147544657, 70.0); + + return poly; + } + + public Polygon makeNonSimplePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(41.987895433319686, 53.75822619011542); + poly.lineTo(41.98789542535497, 53.75822618803151); + poly.lineTo(40.15120412113667, 68.12604154722113); + poly.lineTo(37.72272697311022, 67.92767094118877); + poly.lineTo(37.147347454283086, 49.497473094145505); + poly.lineTo(38.636627026664385, 51.036687142232736); + + poly.startPath(39.00920080789793, 62.063425518369016); + poly.lineTo(38.604912643136885, 70.0); + poly.lineTo(40.71826863485308, 43.60337143116787); + poly.lineTo(35.34407570857744, 54.005512477134126); + poly.lineTo(39.29611825817084, 22.64634933678729); + + return poly; + } + + public Polyline makeNonSimplePolyline() { + // This polyline has a short segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + poly.lineTo(10, 10); + poly.lineTo(10, 5); + poly.lineTo(-5, 5); + + return poly; + }// done + + @Test + public void testIsSimpleBasicsPoint() { + boolean result; + // point is always simple + Point pt = new Point(); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(0, 0); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(100000, 10000); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleBasicsEnvelope() { + // Envelope is simple, when it's width and height are not degenerate + Envelope env = new Envelope(); + boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, + null); // Empty is simple + assertTrue(result); + env.setCoords(0, 0, 10, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver but still simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver and not simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(!result); + }// done + + @Test + public void testIsSimpleBasicsLine() { + Line line = new Line(); + boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, + null, null); + assertTrue(!result); + + line.setStart(new Point(0, 0)); + // line.setEndXY(0, 0); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(!result); + line.setEnd(new Point(1, 0)); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint1() { + MultiPoint mp = new MultiPoint(); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint2FarApart() { + // Two point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(20, 10); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(mp.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointCoincident() { + // Two point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 1); + }// done + + @Test + public void testMultiPointSR4326_CR184439() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorSimplify simpOp = (OperatorSimplify) engine + .getOperator(Operator.Type.Simplify); + NonSimpleResult nonSimpResult = new NonSimpleResult(); + nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(0, 1); + multiPoint.add(0, 0); + Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, + SpatialReference.create(4326), true, nonSimpResult, null); + assertFalse(multiPointIsSimple); + assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); + assertTrue(nonSimpResult.m_vertexIndex1 == 0); + assertTrue(nonSimpResult.m_vertexIndex2 == 2); + } + + @Test + public void testIsSimpleMultiPointCloserThanTolerance() { + // Two point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + MultiPoint mpS; + mp.add(100, 100); + mp.add(100, 100 + sr4326.getTolerance() * .5); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointFarApart2() { + // 5 point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(101, 101); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimpleMultiPoint_coincident2() { + // 5 point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100); + mp.add(11, 1); + mp.add(11, 14); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 4); + assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); + assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); + assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); + }// done + + @Test + public void testIsSimpleMultiPointCloserThanTolerance2() { + // 5 point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100 + sr4326.getTolerance() / 2); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimplePolyline() { + Polyline poly = new Polyline(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolylineFarApart() { + // Two point test: far apart + Polyline poly = new Polyline(); + poly.startPath(20, 10); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCoincident() { + // Two point test: coincident + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCloserThanTolerance() { + // Two point test: closer than tolerance + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap0() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineSelfIntersect() { + // 4 point test: far apart, self intersecting + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateSegment() { + // 4 point test: degenerate segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + { + Polyline other = new Polyline(); + other.startPath(0, 0); + other.lineTo(100, 100); + other.lineTo(100, 0); + other.equals(poly); + } + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartIntersect() { + // 4 point 2 parts test: far apart, intersecting parts + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 0); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartOverlap2() { + // 4 point 2 parts test: far apart, overlapping parts. second part + // starts where first one ends + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 100); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateVertical() { + // 3 point test: degenerate vertical line + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(new Point(100, 100)); + poly.lineTo(new Point(100, 100)); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.getPointCount() == 2); + } + + @Test + public void testIsSimplePolylineEmptyPath() { + // TODO: any way to test this? + // Empty path + // Polyline poly = new Polyline(); + // assertTrue(poly.isEmpty()); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.isEmpty()); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, null); + // assertTrue(result); + } + + @Test + public void testIsSimplePolylineSinglePointInPath() { + // Single point in path + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.removePoint(0, 1); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.isEmpty()); + } + + @Test + public void testIsSimplePolygon() { + Polygon poly = new Polygon(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolygonEmptyPath() { + // TODO: + // Empty path + // Polygon poly = new Polygon(); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.getPathCount() == 1); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, + // null); + // assertTrue(result); + // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, + // sr4326, false, null), sr4326, false, null, null); + // assertTrue(result);// empty is simple + // assertTrue(poly.getPathCount() == 1); + } + + @Test + public void testIsSimplePolygonIncomplete1() { + // Incomplete polygon 1 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonIncomplete2() { + // Incomplete polygon 2 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonDegenerateTriangle() { + // Degenerate triangle (self overlap) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersect() { + // Self intersection - cracking is needed + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole() { + // Rectangle and rectangular hole that has one segment overlapping + // with the with the exterior ring. Cracking is needed. + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole2() { + // Rectangle and rectangular hole + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersectAtVertex() { + // Self intersection at vertex + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygon_2EdgesTouchAtVertex() { + // No self-intersection, but more than two edges touch at the same + // vertex. Simple for ArcGIS, not simple for OGC + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(0, 100); + poly.lineTo(100, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygonTriangle() { + // Triangle + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangle() { + // Rectangle + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHoleWrongDirection() { + // Rectangle and rectangular hole that has wrong direction + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygon_2RectanglesSideBySide() { + // Two rectangles side by side, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(220, -50, 300, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleOneBelow() { + // Two rectangles one below another, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(50, 50, 100, 100), false); + poly.addEnvelope(new Envelope(50, 200, 100, 250), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testisSimpleOGC() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, + null); + assertTrue(result); + + poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(10, 0); + NonSimpleResult nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); + + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 0); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); + assertTrue(result); + + mp = new MultiPoint(); + mp.add(10, 0); + mp.add(10, 0); + nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); + } + + @Test + public void testPolylineIsSimpleForOGC() { + OperatorImportFromJson importerJson = (OperatorImportFromJson) factory + .getOperator(Operator.Type.ImportFromJson); + OperatorSimplify simplify = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + } + + @Test + public void testFillRule() { + //self intersecting star shape + MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); + Polygon poly = (Polygon)mg.getGeometry(); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); + Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); + assertTrue(((Polygon)simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + //solid start without holes: + MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); + boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); + assertTrue(equals); + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 0c84d883..f0f4752a 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -1,85 +1,86 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import java.nio.ByteBuffer; -import junit.framework.TestCase; -import org.junit.Test; - -//import com.vividsolutions.jts.io.WKBReader; - -public class TestWKBSupport extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testWKB() { - // JSON -> GEOM -> WKB - - String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; - // String strPolygon1 = - // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); - Geometry geom = mapGeom.getGeometry(); - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, - // byteBuffer); - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - } - - @Test - public void testWKB2() throws Exception { - // JSON -> GEOM -> WKB - - // String strPolygon1 = - // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; - String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); - Geometry geom = mapGeom.getGeometry(); - - // simplifying geom - OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - SpatialReference sr = SpatialReference.create(102100); - geom = operatorSimplify.execute(geom, sr, true, null); - - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // // checking WKB correctness - // WKBReader jtsReader = new WKBReader(); - // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); - // System.out.println("jtsGeom = " + jtsGeom); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - assertTrue(!geom.isEmpty()); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); - // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - // System.out.println(strPolygon1); - // System.out.println(outputPolygon1); - - } - } +package com.esri.core.geometry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import junit.framework.TestCase; +import org.junit.Test; + +//import com.vividsolutions.jts.io.WKBReader; + +public class TestWKBSupport extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testWKB() { + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + } + + @Test + public void testWKB2() throws Exception { + // JSON -> GEOM -> WKB + + // String strPolygon1 = + // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; + String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + + // simplifying geom + OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + SpatialReference sr = SpatialReference.create(102100); + geom = operatorSimplify.execute(geom, sr, true, null); + + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // // checking WKB correctness + // WKBReader jtsReader = new WKBReader(); + // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); + // System.out.println("jtsGeom = " + jtsGeom); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + assertTrue(!geom.isEmpty()); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); + // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + // System.out.println(strPolygon1); + // System.out.println(outputPolygon1); + + } + +} From b0bc20e9f3a8e6ea880dbfefebeb54d2552b97fe Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 13 Jul 2017 15:01:14 -0700 Subject: [PATCH 044/116] added missing license header (#138) --- .gitignore | 1 + .../core/geometry/AttributeStreamOfDbl.java | 12 +- .../core/geometry/AttributeStreamOfFloat.java | 12 +- .../core/geometry/AttributeStreamOfInt16.java | 12 +- .../core/geometry/AttributeStreamOfInt32.java | 14 +- .../core/geometry/AttributeStreamOfInt64.java | 12 +- .../core/geometry/AttributeStreamOfInt8.java | 12 +- .../ogc/OGCConcreteGeometryCollection.java | 24 + .../com/esri/core/geometry/ogc/OGCCurve.java | 24 + .../esri/core/geometry/ogc/OGCGeometry.java | 24 + .../geometry/ogc/OGCGeometryCollection.java | 24 + .../esri/core/geometry/ogc/OGCLineString.java | 24 + .../esri/core/geometry/ogc/OGCLinearRing.java | 24 + .../esri/core/geometry/ogc/OGCMultiCurve.java | 24 + .../core/geometry/ogc/OGCMultiLineString.java | 24 + .../esri/core/geometry/ogc/OGCMultiPoint.java | 24 + .../core/geometry/ogc/OGCMultiPolygon.java | 24 + .../core/geometry/ogc/OGCMultiSurface.java | 24 + .../com/esri/core/geometry/ogc/OGCPoint.java | 24 + .../esri/core/geometry/ogc/OGCPolygon.java | 24 + .../esri/core/geometry/ogc/OGCSurface.java | 24 + .../com/esri/core/geometry/GeometryUtils.java | 24 + .../geometry/RandomCoordinateGenerator.java | 24 + .../esri/core/geometry/TestAttributes.java | 24 + .../com/esri/core/geometry/TestBuffer.java | 24 + .../java/com/esri/core/geometry/TestClip.java | 24 + .../esri/core/geometry/TestCommonMethods.java | 24 + .../com/esri/core/geometry/TestContains.java | 24 + .../esri/core/geometry/TestConvexHull.java | 24 + .../java/com/esri/core/geometry/TestCut.java | 24 + .../esri/core/geometry/TestDifference.java | 24 + .../com/esri/core/geometry/TestDistance.java | 24 + .../com/esri/core/geometry/TestEditShape.java | 26 +- .../geometry/TestEnvelope2DIntersector.java | 24 + .../com/esri/core/geometry/TestEquals.java | 24 + .../com/esri/core/geometry/TestFailed.java | 24 + .../esri/core/geometry/TestGeneralize.java | 24 + .../com/esri/core/geometry/TestGeodetic.java | 24 + ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 24 + .../esri/core/geometry/TestImportExport.java | 24 + .../geometry/TestInterpolateAttributes.java | 24 + .../esri/core/geometry/TestIntersect2.java | 24 + .../esri/core/geometry/TestIntersection.java | 24 + .../esri/core/geometry/TestIntervalTree.java | 24 + .../esri/core/geometry/TestJSonGeometry.java | 24 + .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 24 + .../esri/core/geometry/TestJsonParser.java | 1205 ++++++++--------- .../com/esri/core/geometry/TestMathUtils.java | 24 + .../esri/core/geometry/TestMultiPoint.java | 24 + .../java/com/esri/core/geometry/TestOGC.java | 24 + .../com/esri/core/geometry/TestOffset.java | 24 + .../com/esri/core/geometry/TestPoint.java | 24 + .../com/esri/core/geometry/TestPolygon.java | 24 + .../esri/core/geometry/TestPolygonUtils.java | 24 + .../esri/core/geometry/TestProximity2D.java | 24 + .../com/esri/core/geometry/TestQuadTree.java | 24 + .../geometry/TestRasterizedGeometry2D.java | 24 + .../com/esri/core/geometry/TestRelation.java | 24 + .../esri/core/geometry/TestSerialization.java | 24 + .../com/esri/core/geometry/TestSimplify.java | 24 + .../core/geometry/TestSpatialReference.java | 24 + .../com/esri/core/geometry/TestTouch.java | 24 + .../com/esri/core/geometry/TestTreap.java | 24 + .../com/esri/core/geometry/TestUnion.java | 24 + .../esri/core/geometry/TestWKBSupport.java | 24 + .../geometry/TestWkbImportOnPostgresST.java | 24 + .../java/com/esri/core/geometry/TestWkid.java | 24 + .../com/esri/core/geometry/TestWktParser.java | 24 + .../java/com/esri/core/geometry/Utils.java | 24 + 69 files changed, 2072 insertions(+), 674 deletions(-) diff --git a/.gitignore b/.gitignore index f31512c4..7328fe5d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ esri-geometry-api.jar description.jardesc *.bak *.properties +*.zip # Intellij project files *.iml diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index bbf40fde..88a639b8 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,14 +31,14 @@ final class AttributeStreamOfDbl extends AttributeStreamBase { - double[] m_buffer = null; - int m_size; + private double[] m_buffer = null; + private int m_size; public int size() { return m_size; } - public void reserve(int reserve)// only in Java + public void reserve(int reserve) { if (reserve <= 0) return; @@ -54,6 +54,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfDbl(int size) { int sz = size; if (sz < 2) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 1e90b35b..491ae221 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfFloat extends AttributeStreamBase { - float[] m_buffer = null; - int m_size; + private float[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfFloat(int size) { int sz = size; if (sz < 2) @@ -314,7 +318,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index d4af7efe..a7d1e175 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfInt16 extends AttributeStreamBase { - short[] m_buffer = null; - int m_size; + private short[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt16(int size) { int sz = size; if (sz < 2) @@ -299,7 +303,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 8aa48e56..6ece2a71 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,10 +31,10 @@ final class AttributeStreamOfInt32 extends AttributeStreamBase { - int[] m_buffer = null; - int m_size; + private int[] m_buffer = null; + private int m_size; - public void reserve(int reserve)// only in Java + public void reserve(int reserve) { if (reserve <= 0) return; @@ -54,6 +54,10 @@ public int size() { return m_size; } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt32(int size) { int sz = size; if (sz < 2) @@ -352,7 +356,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 62516ea2..92688ccd 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfInt64 extends AttributeStreamBase { - long[] m_buffer = null; - int m_size; + private long[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt64(int size) { int sz = size; if (sz < 2) @@ -299,7 +303,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index b0a746e4..a4af8ff5 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfInt8 extends AttributeStreamBase { - byte[] m_buffer = null; - int m_size; + private byte[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt8(int size) { int sz = size; if (sz < 2) @@ -350,7 +354,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 1e4cb7be..3560333c 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.Envelope; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 2cad67f7..30f50dd0 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPoint; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 49197b83..716ee745 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import java.io.IOException; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java index ea0d84af..ef5a3631 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; public abstract class OGCGeometryCollection extends OGCGeometry { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index eb3ee7bb..da51e2d9 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.Geometry; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java index e733f6a5..a4b67d78 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPath; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java index 1084d3e2..9aae3bee 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPath; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 2ea784a3..37006a16 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.GeoJsonExportFlags; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index a57c3b4e..1b0473b8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import java.nio.ByteBuffer; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 878ea168..944e88d5 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.GeoJsonExportFlags; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java index fbb71977..1c98be92 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; public abstract class OGCMultiSurface extends OGCGeometryCollection { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index b6a8f9e0..98b2aaf9 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import java.nio.ByteBuffer; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 5a038d2a..b27e4e48 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.Geometry; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java index 99886778..43d192e6 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; public abstract class OGCSurface extends OGCGeometry { diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index 62ddbc37..2734db7c 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.File; diff --git a/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java index f50f12d7..dcaa0444 100644 --- a/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java +++ b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.Random; diff --git a/src/test/java/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java index bd59cc9b..16c1d9df 100644 --- a/src/test/java/com/esri/core/geometry/TestAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestAttributes.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import static org.junit.Assert.*; diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java index f381234b..341751f5 100755 --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index 9653e5fa..3dcca075 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java index b38aeec4..9b937d4b 100644 --- a/src/test/java/com/esri/core/geometry/TestCommonMethods.java +++ b/src/test/java/com/esri/core/geometry/TestCommonMethods.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.File; diff --git a/src/test/java/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java index 4488b3ef..d6da4af7 100644 --- a/src/test/java/com/esri/core/geometry/TestContains.java +++ b/src/test/java/com/esri/core/geometry/TestContains.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index 62c59c2f..b2e2d59e 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index 8bb8f25c..456973cd 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index 455877e6..c6f22321 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java index efcdaeeb..50399967 100644 --- a/src/test/java/com/esri/core/geometry/TestDistance.java +++ b/src/test/java/com/esri/core/geometry/TestDistance.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java index 173a4970..95b5ea30 100644 --- a/src/test/java/com/esri/core/geometry/TestEditShape.java +++ b/src/test/java/com/esri/core/geometry/TestEditShape.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; @@ -17,8 +41,6 @@ protected void tearDown() throws Exception { @Test public static void testEditShape() { { - // std::shared_ptr poly_base_6 - // = std::make_shared(); // Single part polygon Polygon poly = new Polygon(); poly.startPath(10, 10); diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java index b7bc7d2e..2f33fadb 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestEquals.java b/src/test/java/com/esri/core/geometry/TestEquals.java index a42abf30..90ed71a0 100644 --- a/src/test/java/com/esri/core/geometry/TestEquals.java +++ b/src/test/java/com/esri/core/geometry/TestEquals.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index 5c5b6c41..05bede19 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index 5348d20d..34d497e4 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 35c805ea..93185af9 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 498dab4b..1a0bf432 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 5fd97f10..0b9e2bc8 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.nio.ByteBuffer; diff --git a/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java index de462a02..f20f3063 100644 --- a/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java index 8bdcd3c8..36860635 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersect2.java +++ b/src/test/java/com/esri/core/geometry/TestIntersect2.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.Geometry.Type; diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index 8e3f7fcc..eb6a2a73 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestIntervalTree.java b/src/test/java/com/esri/core/geometry/TestIntervalTree.java index 0b12d678..4c0cde97 100644 --- a/src/test/java/com/esri/core/geometry/TestIntervalTree.java +++ b/src/test/java/com/esri/core/geometry/TestIntervalTree.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java index a996575f..62342524 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java +++ b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.HashMap; diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 8b036785..a6ac4d96 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index 9f2d887f..96f6343c 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.Hashtable; @@ -14,651 +38,538 @@ public class TestJsonParser extends TestCase { - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } - - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory - .createJsonParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory - .createJsonParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText() - .equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 - .getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory - .createJsonParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory - .createJsonParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory - .createJsonParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory - .createJsonParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine - .jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06298);// -97.06153, 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) - .getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06153);// -97.06153, 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - Assert.assertTrue("Should not throw for invalid wkid", false); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine - .geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), - pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), - pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), - pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), - pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), - pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), - pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), - pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), - pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out - .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) { - System.out.println("No spatial reference"); - } else { - System.out.println("wkid: " - + geom.getSpatialReference().getID()); - } - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() - + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() - + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " - + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - //System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - //System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson( - SpatialReference.create(3857), geom);// Test WKID == -1 - String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createJsonParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference.create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP.getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()).getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine.geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + + pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine.jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()).getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP.getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine.jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP.getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine.jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory.createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory.createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory.createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson(mapGeom2.getSpatialReference(), + mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory.createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3.getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3.getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText().equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3.getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory.createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory.createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory.createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory.createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine.jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06298);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()).getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06153);// -97.06153, + // 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + Assert.assertTrue("Should not throw for invalid wkid", false); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine.geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out.println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) { + System.out.println("No spatial reference"); + } else { + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + // System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + // System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson(SpatialReference.create(3857), geom);// Test + // WKID + // == + // -1 + String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } } diff --git a/src/test/java/com/esri/core/geometry/TestMathUtils.java b/src/test/java/com/esri/core/geometry/TestMathUtils.java index 0d41a739..c6d39735 100644 --- a/src/test/java/com/esri/core/geometry/TestMathUtils.java +++ b/src/test/java/com/esri/core/geometry/TestMathUtils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestMultiPoint.java b/src/test/java/com/esri/core/geometry/TestMultiPoint.java index b5b7d534..cd8e8d70 100644 --- a/src/test/java/com/esri/core/geometry/TestMultiPoint.java +++ b/src/test/java/com/esri/core/geometry/TestMultiPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 3edf417c..50fdcf5f 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java index 1bd18b9a..385e3504 100644 --- a/src/test/java/com/esri/core/geometry/TestOffset.java +++ b/src/test/java/com/esri/core/geometry/TestOffset.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.OperatorOffset.JoinType; diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index 0140198a..c2e8bd2f 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.Random; diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 8b918e9c..2c4b0b4f 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestPolygonUtils.java b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java index 16bc5215..2967d2f7 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygonUtils.java +++ b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java index f06c8ce9..9f6cd184 100644 --- a/src/test/java/com/esri/core/geometry/TestProximity2D.java +++ b/src/test/java/com/esri/core/geometry/TestProximity2D.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index 6dde9220..9c4800ad 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java index a1ff65fc..c8c835be 100644 --- a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java +++ b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 041908cf..4ecbf514 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 269b0879..8cdb4ad9 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.ByteArrayInputStream; diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index 47a741c1..944d271d 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; //import java.io.FileOutputStream; diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReference.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java index 8d21712f..c002f977 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReference.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestTouch.java b/src/test/java/com/esri/core/geometry/TestTouch.java index b677ba7d..8be45c11 100644 --- a/src/test/java/com/esri/core/geometry/TestTouch.java +++ b/src/test/java/com/esri/core/geometry/TestTouch.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestTreap.java b/src/test/java/com/esri/core/geometry/TestTreap.java index 3bd20606..e1f2b5b5 100644 --- a/src/test/java/com/esri/core/geometry/TestTreap.java +++ b/src/test/java/com/esri/core/geometry/TestTreap.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index ad06dbbc..55392e39 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index f0f4752a..a55252fb 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java index ccac6c5f..95c55d2b 100644 --- a/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java +++ b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.nio.ByteBuffer; diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index cd249233..9bac7c5b 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import static org.junit.Assert.*; diff --git a/src/test/java/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java index db40cdff..71ce5b1d 100644 --- a/src/test/java/com/esri/core/geometry/TestWktParser.java +++ b/src/test/java/com/esri/core/geometry/TestWktParser.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import static org.junit.Assert.*; diff --git a/src/test/java/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java index 9bbe51c0..d9923281 100644 --- a/src/test/java/com/esri/core/geometry/Utils.java +++ b/src/test/java/com/esri/core/geometry/Utils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; public class Utils { From 43fbcaaad1ebdfb9f1c832335c3acee7f8a2943c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sat, 12 Aug 2017 00:06:48 -0700 Subject: [PATCH 045/116] rename equals to Equals (#143) --- .../com/esri/core/geometry/ogc/OGCCurve.java | 1 - .../esri/core/geometry/ogc/OGCGeometry.java | 18 +++++++++--- .../esri/core/geometry/ogc/OGCMultiPoint.java | 2 -- .../com/esri/core/geometry/ogc/OGCPoint.java | 1 - .../java/com/esri/core/geometry/TestOGC.java | 28 ++++++++++++------- 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 30f50dd0..0755dc2e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -25,7 +25,6 @@ package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.Point; public abstract class OGCCurve extends OGCGeometry { public abstract double length(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 716ee745..7bcc17c8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -24,7 +24,6 @@ package com.esri.core.geometry.ogc; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -205,16 +204,27 @@ public boolean isMeasured() { abstract public OGCGeometry boundary(); /** - * OGC equals - * + * OGC equals. Performs topological comparison with tolerance. + * This is different from equals(Object), that uses exact comparison. */ - public boolean equals(OGCGeometry another) { + public boolean Equals(OGCGeometry another) { + if (this == another) + return true; + + if (another == null) + return false; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, getEsriSpatialReference()); } + @Deprecated + public boolean equals(OGCGeometry another) { + return Equals(another); + } + public boolean disjoint(OGCGeometry another) { com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 1b0473b8..77258957 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -27,13 +27,11 @@ import java.nio.ByteBuffer; import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorExportToWkb; import com.esri.core.geometry.OperatorFactoryLocal; -import com.esri.core.geometry.OperatorUnion; import com.esri.core.geometry.Point; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 98b2aaf9..7e246a6e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -26,7 +26,6 @@ import java.nio.ByteBuffer; -import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Operator; diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 50fdcf5f..ca2bcf6d 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -82,28 +82,36 @@ public void testPolygon() throws Exception { OGCLineString ls = p.exteriorRing(); // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); boolean b = ls - .equals(OGCGeometry + .Equals(OGCGeometry .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)")); assertTrue(b); OGCLineString lsi = p.interiorRingN(0); - b = lsi.equals(OGCGeometry + b = lsi.Equals(OGCGeometry .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(b); b = lsi.equals((Object)OGCGeometry .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); - assertTrue(!lsi.equals(ls)); + assertTrue(!lsi.Equals(ls)); OGCMultiCurve boundary = p.boundary(); String s = boundary.asText(); assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); { - OGCGeometry g2 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); - OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}").intersects(g2); - OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}").intersects(g2); - - OGCGeometry g3 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); - boolean bb = g2.equals((Object)g3); - assertTrue(bb); + OGCGeometry g2 = OGCGeometry.fromGeoJson( + "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + OGCGeometry + .fromGeoJson( + "{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}") + .intersects(g2); + OGCGeometry + .fromGeoJson( + "{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}") + .intersects(g2); + + OGCGeometry g3 = OGCGeometry.fromGeoJson( + "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + boolean bb = g2.equals((Object) g3); + assertTrue(bb); } } From 630a46e35db64d112a79fbe0ea587a57a32ac7f6 Mon Sep 17 00:00:00 2001 From: GISDev01 Date: Thu, 30 Nov 2017 13:19:42 -0500 Subject: [PATCH 046/116] Update Maven coordinates to the latest stable version of the Esri Geometry API for Java, 2.0.0 (#148) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d243c1..ca345997 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 1.2.1 + 2.0.0 ``` From e2f5fadf29e517618ff3c5f109f64e837d43220f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 4 Dec 2017 10:00:04 -0800 Subject: [PATCH 047/116] Update .travis.yml (#149) * Update .travis.yml Update to use JDKs that are available: https://docs.travis-ci.com/user/reference/trusty/#JVM-(Clojure%2C-Groovy%2C-Java%2C-Scala)-images * Update pom.xml Update javadoc plugin version --- .travis.yml | 7 ++++--- pom.xml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7406d2b7..d1fa4fad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: java jdk: - - openjdk6 - openjdk7 - - oraclejdk7 + - openjdk8 + - oraclejdk8 + - oraclejdk9 notifications: - email: false \ No newline at end of file + email: false diff --git a/pom.xml b/pom.xml index 6738b3ae..bb048091 100755 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,7 @@ 2.3.1 2.2.1 - 2.9 + 3.0.0-M1 From 3704c2205b435788573303a0698a082734ae76c4 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 29 Dec 2017 16:14:11 -0800 Subject: [PATCH 048/116] README: update copyright to 2018 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca345997..34589c2f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2017 Esri +Copyright 2013-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 1adecb86f11052e835cf1bb4ae97fd13bb49d4de Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 5 Mar 2018 13:32:15 -0500 Subject: [PATCH 049/116] Add OGCGeometry::estimateMemorySize API (#157) --- pom.xml | 9 ++ .../core/geometry/AttributeStreamBase.java | 7 + .../core/geometry/AttributeStreamOfDbl.java | 11 ++ .../core/geometry/AttributeStreamOfFloat.java | 11 +- .../core/geometry/AttributeStreamOfInt16.java | 11 +- .../core/geometry/AttributeStreamOfInt32.java | 11 +- .../core/geometry/AttributeStreamOfInt64.java | 11 +- .../core/geometry/AttributeStreamOfInt8.java | 10 ++ .../java/com/esri/core/geometry/Envelope.java | 10 +- .../com/esri/core/geometry/Envelope2D.java | 9 +- .../java/com/esri/core/geometry/Geometry.java | 20 ++- .../java/com/esri/core/geometry/Line.java | 8 ++ .../com/esri/core/geometry/MultiPathImpl.java | 24 +++- .../com/esri/core/geometry/MultiPoint.java | 8 ++ .../esri/core/geometry/MultiPointImpl.java | 16 ++- .../java/com/esri/core/geometry/Point.java | 10 +- .../java/com/esri/core/geometry/Polygon.java | 8 +- .../java/com/esri/core/geometry/Polyline.java | 7 + .../java/com/esri/core/geometry/SizeOf.java | 127 ++++++++++++++++++ .../esri/core/geometry/SpatialReference.java | 7 +- .../core/geometry/SpatialReferenceImpl.java | 13 -- .../ogc/OGCConcreteGeometryCollection.java | 15 +++ .../esri/core/geometry/ogc/OGCGeometry.java | 46 ++++++- .../esri/core/geometry/ogc/OGCLineString.java | 10 ++ .../core/geometry/ogc/OGCMultiLineString.java | 10 +- .../esri/core/geometry/ogc/OGCMultiPoint.java | 12 +- .../core/geometry/ogc/OGCMultiPolygon.java | 9 ++ .../com/esri/core/geometry/ogc/OGCPoint.java | 12 +- .../esri/core/geometry/ogc/OGCPolygon.java | 9 ++ .../core/geometry/TestEstimateMemorySize.java | 106 +++++++++++++++ 30 files changed, 539 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/SizeOf.java create mode 100644 src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java diff --git a/pom.xml b/pom.xml index bb048091..f9e62a0c 100755 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,7 @@ 2.6.5 4.12 + 0.2 2.3.1 @@ -120,6 +121,14 @@ ${junit.version} test + + + + org.openjdk.jol + jol-core + ${jol.version} + test + diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java index 54f03ba2..2755406a 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java @@ -46,6 +46,13 @@ public AttributeStreamBase() { */ public abstract int virtualSize(); + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); + /** * Returns the Persistence type of the stream. */ diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 88a639b8..75d45a76 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -26,9 +26,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; import java.util.Arrays; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32; +import static com.esri.core.geometry.SizeOf.sizeOfDoubleArray; + final class AttributeStreamOfDbl extends AttributeStreamBase { private double[] m_buffer = null; @@ -173,6 +178,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_DBL + sizeOfDoubleArray(m_buffer.length); + } + // @Override // public void addRange(AttributeStreamBase src, int srcStartIndex, int // count) { diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 491ae221..95ef8c5f 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -26,10 +26,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; -final class AttributeStreamOfFloat extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT; +import static com.esri.core.geometry.SizeOf.sizeOfFloatArray; +final class AttributeStreamOfFloat extends AttributeStreamBase { private float[] m_buffer = null; private int m_size; @@ -145,6 +148,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT + sizeOfFloatArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumFloat; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index a7d1e175..987ffae3 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -26,10 +26,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; -final class AttributeStreamOfInt16 extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16; +import static com.esri.core.geometry.SizeOf.sizeOfShortArray; +final class AttributeStreamOfInt16 extends AttributeStreamBase { private short[] m_buffer = null; private int m_size; @@ -145,6 +148,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 + sizeOfShortArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt16; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 6ece2a71..1939bb0f 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -26,11 +26,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; import java.util.Arrays; -final class AttributeStreamOfInt32 extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; +final class AttributeStreamOfInt32 extends AttributeStreamBase { private int[] m_buffer = null; private int m_size; @@ -158,6 +161,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 + sizeOfIntArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt32; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 92688ccd..99376590 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -26,10 +26,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; -final class AttributeStreamOfInt64 extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64; +import static com.esri.core.geometry.SizeOf.sizeOfLongArray; +final class AttributeStreamOfInt64 extends AttributeStreamBase { private long[] m_buffer = null; private int m_size; @@ -145,6 +148,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 + sizeOfLongArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt64; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index a4af8ff5..a93d154a 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -26,8 +26,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8; +import static com.esri.core.geometry.SizeOf.sizeOfByteArray; + final class AttributeStreamOfInt8 extends AttributeStreamBase { private byte[] m_buffer = null; @@ -152,6 +156,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 + sizeOfByteArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt8; diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 1370ca4f..ca884b55 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -25,9 +25,11 @@ package com.esri.core.geometry; +import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; -import com.esri.core.geometry.VertexDescription.Semantics; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ENVELOPE; /** * An envelope is an axis-aligned rectangle. @@ -445,6 +447,12 @@ public int getDimension() { return 2; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ENVELOPE + m_envelope.estimateMemorySize() + estimateMemorySize(m_attributes); + } + @Override public void queryEnvelope(Envelope env) { copyTo(env); diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 172619dd..fa41db68 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -28,12 +28,14 @@ import java.io.ObjectStreamException; import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ENVELOPE2D; + /** * An axis parallel 2-dimensional rectangle. */ public final class Envelope2D implements Serializable { private static final long serialVersionUID = 1L; - + private final static int XLESSXMIN = 1; // private final int XGREATERXMAX = 2; private final static int YLESSYMIN = 4; @@ -79,6 +81,11 @@ public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { public Envelope2D(Envelope2D other) { setCoords(other); } + + public int estimateMemorySize() + { + return SIZE_OF_ENVELOPE2D; + } public void setCoords(double _x, double _y) { xmin = _x; diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 68e93671..01614b14 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; -import com.esri.core.geometry.VertexDescription.Semantics; - import java.io.ObjectStreamException; import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.sizeOfDoubleArray; + /** * Common properties and methods shared by all geometric objects. Geometries are * objects that define a spatial location and and associated geometric shape. @@ -150,6 +150,22 @@ static public Geometry.Type intToType(int geometryType) */ public abstract int getDimension(); + /** + * Returns an estimate of this object size in bytes. + *

+ * This estimate doesn't include the size of the {@link VertexDescription} object + * because instances of {@link VertexDescription} are shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); + + protected static long estimateMemorySize(double[] attributes) + { + return attributes != null ? sizeOfDoubleArray(attributes.length) : 0; + } + /** * Returns the VertexDescription of this geomtry. */ diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 4eccd513..90b08561 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -29,6 +29,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_LINE; + /** * A straight line between a pair of points. * @@ -40,6 +42,12 @@ public Geometry.Type getType() { return Type.Line; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_LINE + estimateMemorySize(m_attributes); + } + @Override public double calculateLength2D() { double dx = m_xStart - m_xEnd; diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index dce85615..54ec0a5d 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -25,10 +25,9 @@ package com.esri.core.geometry; -import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MULTI_PATH_IMPL; final class MultiPathImpl extends MultiVertexGeometryImpl { - protected boolean m_bPolygon; protected Point m_moveToPoint; protected double m_cachedLength2D; @@ -60,6 +59,27 @@ final class MultiPathImpl extends MultiVertexGeometryImpl { // Bezier, XXX, Arc, // XXX; + @Override + public long estimateMemorySize() + { + long size = SIZE_OF_MULTI_PATH_IMPL + + + (m_envelope != null ? m_envelope.estimateMemorySize() : 0) + + (m_moveToPoint != null ? m_moveToPoint.estimateMemorySize() : 0) + + (m_cachedRingAreas2D != null ? m_cachedRingAreas2D.estimateMemorySize() : 0) + + m_paths.estimateMemorySize() + + m_pathFlags.estimateMemorySize() + + (m_segmentFlags != null ? m_segmentFlags.estimateMemorySize() : 0) + + (m_segmentParamIndex != null ? m_segmentParamIndex.estimateMemorySize() : 0) + + (m_segmentParams != null ? m_segmentParams.estimateMemorySize() : 0); + + if (m_vertexAttributes != null) { + for (int i = 0; i < m_vertexAttributes.length; i++) { + size += m_vertexAttributes[i].estimateMemorySize(); + } + } + return size; + } + public boolean hasNonLinearSegments() { return m_curveParamwritePoint > 0; } diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 8a3f6f6a..4beca5b5 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -26,6 +26,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MULTI_POINT; + /** * A Multipoint is a collection of points. A multipoint is a one-dimensional * geometry object. Multipoints can be used to store a collection of point-based @@ -258,6 +260,12 @@ public int getDimension() { return 0; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_MULTI_POINT + m_impl.estimateMemorySize(); + } + @Override public Geometry.Type getType() { return Type.MultiPoint; diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index b3d0a4b7..bb16671f 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -26,11 +26,12 @@ import com.esri.core.geometry.VertexDescription.Semantics; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MULTI_POINT_IMPL; + /** * The MultiPoint is a collection of points. */ final class MultiPointImpl extends MultiVertexGeometryImpl { - public MultiPointImpl() { super(); m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); @@ -247,6 +248,19 @@ public int getDimension() { return 0; } + @Override + public long estimateMemorySize() + { + long size = SIZE_OF_MULTI_POINT_IMPL + (m_envelope != null ? m_envelope.estimateMemorySize() : 0); + + if (m_vertexAttributes != null) { + for (int i = 0; i < m_vertexAttributes.length; i++) { + size += m_vertexAttributes[i].estimateMemorySize(); + } + } + return size; + } + @Override public Geometry.Type getType() { return Type.MultiPoint; diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 2343c5df..d96589ae 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -28,6 +28,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_POINT; + /** * A Point is a zero-dimensional object that represents a specific (X,Y) * location in a two-dimensional XY-Plane. In case of Geographic Coordinate @@ -37,7 +39,7 @@ public class Point extends Geometry implements Serializable { //We are using writeReplace instead. //private static final long serialVersionUID = 2L; - double[] m_attributes; // use doubles to store everything (long are bitcast) + double[] m_attributes; // use doubles to store everything (long are bitcast) /** * Creates an empty 2D point. @@ -369,6 +371,12 @@ public int getDimension() { return 0; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_POINT + estimateMemorySize(m_attributes); + } + @Override public void setEmpty() { _touch(); diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index cb027357..a8298077 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -27,11 +27,12 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_POLYGON; + /** * A polygon is a collection of one or many interior or exterior rings. */ public class Polygon extends MultiPath implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and // GeometrySerializer @@ -62,6 +63,11 @@ public Geometry.Type getType() { return Type.Polygon; } + @Override + public long estimateMemorySize() { + return SIZE_OF_POLYGON + m_impl.estimateMemorySize(); + } + /** * Calculates the ring area for this ring. * diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 4c83a147..0d842806 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -27,6 +27,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_POLYLINE; + /** * A polyline is a collection of one or many paths. * @@ -72,6 +74,11 @@ public Geometry.Type getType() { return Type.Polyline; } + @Override + public long estimateMemorySize() { + return SIZE_OF_POLYLINE + m_impl.estimateMemorySize(); + } + /** * Returns TRUE when this geometry has exactly same type, properties, and * coordinates as the other geometry. diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java new file mode 100644 index 00000000..86683a93 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -0,0 +1,127 @@ +/* + Copyright 1995-2018 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_BYTE_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_CHAR_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_CHAR_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_DOUBLE_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_DOUBLE_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_FLOAT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_FLOAT_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_INT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_INT_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_LONG_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_LONG_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_SHORT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_SHORT_INDEX_SCALE; + +public final class SizeOf +{ + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_DBL = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 = 24; + + public static final int SIZE_OF_ENVELOPE = 32; + + public static final int SIZE_OF_ENVELOPE2D = 48; + + public static final int SIZE_OF_LINE = 56; + + public static final int SIZE_OF_MULTI_PATH = 24; + + public static final int SIZE_OF_MULTI_PATH_IMPL = 112; + + public static final int SIZE_OF_MULTI_POINT = 24; + + public static final int SIZE_OF_MULTI_POINT_IMPL = 56; + + public static final int SIZE_OF_POINT = 24; + + public static final int SIZE_OF_POLYGON = 24; + + public static final int SIZE_OF_POLYLINE = 24; + + public static final int SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION = 24; + + public static final int SIZE_OF_OGC_LINE_STRING = 24; + + public static final int SIZE_OF_OGC_MULTI_LINE_STRING = 24; + + public static final int SIZE_OF_OGC_MULTI_POINT = 24; + + public static final int SIZE_OF_OGC_MULTI_POLYGON = 24; + + public static final int SIZE_OF_OGC_POINT = 24; + + public static final int SIZE_OF_OGC_POLYGON = 24; + + public static long sizeOfByteArray(int length) + { + return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); + } + + public static long sizeOfShortArray(int length) + { + return ARRAY_SHORT_BASE_OFFSET + (((long) ARRAY_SHORT_INDEX_SCALE) * length); + } + + public static long sizeOfCharArray(int length) + { + return ARRAY_CHAR_BASE_OFFSET + (((long) ARRAY_CHAR_INDEX_SCALE) * length); + } + + public static long sizeOfIntArray(int length) + { + return ARRAY_INT_BASE_OFFSET + (((long) ARRAY_INT_INDEX_SCALE) * length); + } + + public static long sizeOfLongArray(int length) + { + return ARRAY_LONG_BASE_OFFSET + (((long) ARRAY_LONG_INDEX_SCALE) * length); + } + + public static long sizeOfFloatArray(int length) + { + return ARRAY_FLOAT_BASE_OFFSET + (((long) ARRAY_FLOAT_INDEX_SCALE) * length); + } + + public static long sizeOfDoubleArray(int length) + { + return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); + } + + private SizeOf() + { + } +} diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 44fe3912..22b0c74f 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -24,14 +24,11 @@ package com.esri.core.geometry; +import com.fasterxml.jackson.core.JsonParser; + import java.io.ObjectStreamException; import java.io.Serializable; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.SpatialReferenceSerializer; -import com.esri.core.geometry.VertexDescription; -import com.fasterxml.jackson.core.JsonParser; - /** * A class that represents the spatial reference for the geometry. * This class provide tolerance value for the topological and relational operations. diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 618eb6b8..25158369 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -25,20 +25,7 @@ package com.esri.core.geometry; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.locks.ReentrantLock; -import java.lang.ref.*; - -import com.esri.core.geometry.Envelope2D; -import com.esri.core.geometry.GeoDist; -import com.esri.core.geometry.GeometryException; -import com.esri.core.geometry.PeDouble; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.SpatialReferenceImpl; -import com.esri.core.geometry.VertexDescription.Semantics; class SpatialReferenceImpl extends SpatialReference { static final boolean no_projection_engine = true; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 3560333c..51e171e7 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -32,11 +32,14 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; + public class OGCConcreteGeometryCollection extends OGCGeometryCollection { public OGCConcreteGeometryCollection(List geoms, SpatialReference sr) { @@ -104,6 +107,18 @@ public String geometryType() { return "GeometryCollection"; } + @Override + public long estimateMemorySize() + { + long size = SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; + if (geometries != null) { + for (OGCGeometry geometry : geometries) { + size += geometry.estimateMemorySize(); + } + } + return size; + } + @Override public String asText() { StringBuilder sb = new StringBuilder("GEOMETRYCOLLECTION "); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 7bcc17c8..4ad748be 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -24,12 +24,43 @@ package com.esri.core.geometry.ogc; +import com.esri.core.geometry.Envelope; +import com.esri.core.geometry.Envelope1D; +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryCursorAppend; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.JsonParserReader; +import com.esri.core.geometry.MapGeometry; +import com.esri.core.geometry.MapOGCStructure; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.OGCStructure; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorBuffer; +import com.esri.core.geometry.OperatorConvexHull; +import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.OperatorImportFromESRIShape; +import com.esri.core.geometry.OperatorImportFromGeoJson; +import com.esri.core.geometry.OperatorImportFromWkb; +import com.esri.core.geometry.OperatorImportFromWkt; +import com.esri.core.geometry.OperatorIntersection; +import com.esri.core.geometry.OperatorSimplify; +import com.esri.core.geometry.OperatorSimplifyOGC; +import com.esri.core.geometry.OperatorUnion; +import com.esri.core.geometry.Point; +import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SimpleGeometryCursor; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.VertexDescription; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; -import com.esri.core.geometry.*; - /** * OGC Simple Feature Access specification v.1.2.1 * @@ -53,6 +84,17 @@ public int coordinateDimension() { abstract public String geometryType(); + /** + * Returns an estimate of this object size in bytes. + *

+ * This estimate doesn't include the size of the {@link SpatialReference} object + * because instances of {@link SpatialReference} are expected to be shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); + public int SRID() { if (esriSR == null) return 0; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index da51e2d9..464b9a7c 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -34,9 +34,13 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_LINE_STRING; + public class OGCLineString extends OGCCurve { + /** * The number of Points in this LineString. */ @@ -116,6 +120,12 @@ public String geometryType() { return "LineString"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_LINE_STRING + (multiPath != null ? multiPath.estimateMemorySize() : 0); + } + @Override public OGCGeometry locateAlong(double mValue) { throw new UnsupportedOperationException(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 37006a16..8fa020c8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -36,10 +36,12 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; -public class OGCMultiLineString extends OGCMultiCurve { +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING; +public class OGCMultiLineString extends OGCMultiCurve { public OGCMultiLineString(Polyline poly, SpatialReference sr) { polyline = poly; esriSR = sr; @@ -75,6 +77,12 @@ public String geometryType() { return "MultiLineString"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_MULTI_LINE_STRING + (polyline != null ? polyline.estimateMemorySize() : 0); + } + @Override public OGCGeometry boundary() { OperatorBoundary op = (OperatorBoundary) OperatorFactoryLocal diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 77258957..b25a948a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -24,8 +24,6 @@ package com.esri.core.geometry.ogc; -import java.nio.ByteBuffer; - import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; @@ -37,6 +35,10 @@ import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POINT; + public class OGCMultiPoint extends OGCGeometryCollection { public int numGeometries() { return multiPoint.getPointCount(); @@ -66,6 +68,12 @@ public String geometryType() { return "MultiPoint"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_MULTI_POINT + (multiPoint != null ? multiPoint.estimateMemorySize() : 0); + } + /** * * @param mp diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 944e88d5..bed0e114 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -36,8 +36,11 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POLYGON; + public class OGCMultiPolygon extends OGCMultiSurface { public OGCMultiPolygon(Polygon src, SpatialReference sr) { @@ -89,6 +92,12 @@ public String geometryType() { return "MultiPolygon"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_MULTI_POLYGON + (polygon != null ? polygon.estimateMemorySize() : 0); + } + @Override public OGCGeometry boundary() { Polyline polyline = new Polyline(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 7e246a6e..9db01268 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -24,8 +24,6 @@ package com.esri.core.geometry.ogc; -import java.nio.ByteBuffer; - import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Operator; @@ -36,6 +34,10 @@ import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POINT; + public final class OGCPoint extends OGCGeometry { public OGCPoint(Point pt, SpatialReference sr) { point = pt; @@ -77,6 +79,12 @@ public String geometryType() { return "Point"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_POINT + (point != null ? point.estimateMemorySize() : 0); + } + @Override public OGCGeometry boundary() { return new OGCMultiPoint(new MultiPoint(getEsriGeometry() diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index b27e4e48..6f7a74f2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -34,8 +34,11 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POLYGON; + public class OGCPolygon extends OGCSurface { public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { polygon = new Polygon(); @@ -109,6 +112,12 @@ public String geometryType() { return "Polygon"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_POLYGON + (polygon != null ? polygon.estimateMemorySize() : 0); + } + @Override public OGCGeometry locateAlong(double mValue) { // TODO Auto-generated method stub diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java new file mode 100644 index 00000000..ba516b5f --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -0,0 +1,106 @@ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCLineString; +import com.esri.core.geometry.ogc.OGCMultiLineString; +import com.esri.core.geometry.ogc.OGCMultiPoint; +import com.esri.core.geometry.ogc.OGCMultiPolygon; +import com.esri.core.geometry.ogc.OGCPoint; +import com.esri.core.geometry.ogc.OGCPolygon; +import org.junit.Test; +// ClassLayout is GPL with Classpath exception, see http://openjdk.java.net/legal/gplv2+ce.html +import org.openjdk.jol.info.ClassLayout; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestEstimateMemorySize +{ + @Test + public void testInstanceSizes() + { + assertEquals(getInstanceSize(AttributeStreamOfFloat.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT); + assertEquals(getInstanceSize(AttributeStreamOfDbl.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL); + assertEquals(getInstanceSize(AttributeStreamOfInt8.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8); + assertEquals(getInstanceSize(AttributeStreamOfInt16.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16); + assertEquals(getInstanceSize(AttributeStreamOfInt32.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32); + assertEquals(getInstanceSize(AttributeStreamOfInt64.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64); + assertEquals(getInstanceSize(Envelope.class), SizeOf.SIZE_OF_ENVELOPE); + assertEquals(getInstanceSize(Envelope2D.class), SizeOf.SIZE_OF_ENVELOPE2D); + assertEquals(getInstanceSize(Line.class), SizeOf.SIZE_OF_LINE); + assertEquals(getInstanceSize(MultiPath.class), SizeOf.SIZE_OF_MULTI_PATH); + assertEquals(getInstanceSize(MultiPathImpl.class), SizeOf.SIZE_OF_MULTI_PATH_IMPL); + assertEquals(getInstanceSize(MultiPoint.class), SizeOf.SIZE_OF_MULTI_POINT); + assertEquals(getInstanceSize(MultiPointImpl.class), SizeOf.SIZE_OF_MULTI_POINT_IMPL); + assertEquals(getInstanceSize(Point.class), SizeOf.SIZE_OF_POINT); + assertEquals(getInstanceSize(Polygon.class), SizeOf.SIZE_OF_POLYGON); + assertEquals(getInstanceSize(Polyline.class), SizeOf.SIZE_OF_POLYLINE); + assertEquals(getInstanceSize(OGCConcreteGeometryCollection.class), SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION); + assertEquals(getInstanceSize(OGCLineString.class), SizeOf.SIZE_OF_OGC_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiLineString.class), SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiPoint.class), SizeOf.SIZE_OF_OGC_MULTI_POINT); + assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); + assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); + assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); + } + + private static int getInstanceSize(Class clazz) + { + return ClassLayout.parseClass(clazz).instanceSize(); + } + + @Test + public void testPoint() + { + testGeometry(parseWkt("POINT (1 2)")); + } + + @Test + public void testMultiPoint() + { + testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); + } + + @Test + public void testLineString() + { + testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); + } + + @Test + public void testMultiLineString() + { + testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); + } + + @Test + public void testPolygon() + { + testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); + } + + @Test + public void testMultiPolygon() + { + testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); + } + + @Test + public void testGeometryCollection() + { + testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); + } + + private void testGeometry(OGCGeometry geometry) + { + assertTrue(geometry.estimateMemorySize() > 0); + } + + private static OGCGeometry parseWkt(String wkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + geometry.setSpatialReference(null); + return geometry; + } +} From 9ba95a6b212c30fe8e0ed96bcb827414e7ddbbcb Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 5 Mar 2018 15:31:39 -0800 Subject: [PATCH 050/116] Stolstov/update jackson (#158) * fixed some errors in javadoc and updated jol to 0.9 * updated jackson to 2.9.4 * remove jars from DepFiles --- DepFiles/public/jackson-core-2.6.2.jar | Bin 258824 -> 0 bytes DepFiles/unittest/junit-4.12.jar | Bin 314932 -> 0 bytes build.xml | 8 +- pom.xml | 8 +- .../esri/core/geometry/CombineOperator.java | 3 +- .../java/com/esri/core/geometry/Envelope.java | 46 +++--- .../com/esri/core/geometry/Envelope1D.java | 4 +- .../com/esri/core/geometry/Envelope2D.java | 42 ++++- .../java/com/esri/core/geometry/Geometry.java | 99 +++++++----- .../esri/core/geometry/GeometryEngine.java | 8 +- .../com/esri/core/geometry/MapGeometry.java | 20 ++- .../geometry/OperatorDensifyByLength.java | 4 +- .../geometry/OperatorImportFromGeoJson.java | 4 +- .../core/geometry/OperatorIntersection.java | 8 +- .../esri/core/geometry/OperatorOffset.java | 4 +- .../java/com/esri/core/geometry/Point.java | 38 ++--- .../java/com/esri/core/geometry/Point2D.java | 4 +- .../java/com/esri/core/geometry/Polygon.java | 74 ++++----- .../java/com/esri/core/geometry/Polyline.java | 2 +- .../java/com/esri/core/geometry/SizeOf.java | 106 ++++++------- .../esri/core/geometry/SpatialReference.java | 2 +- .../esri/core/geometry/ogc/OGCGeometry.java | 2 +- .../core/geometry/TestEstimateMemorySize.java | 145 ++++++++---------- 23 files changed, 342 insertions(+), 289 deletions(-) delete mode 100644 DepFiles/public/jackson-core-2.6.2.jar delete mode 100644 DepFiles/unittest/junit-4.12.jar diff --git a/DepFiles/public/jackson-core-2.6.2.jar b/DepFiles/public/jackson-core-2.6.2.jar deleted file mode 100644 index a7d87f067340ac378230a8ccf8b4dfc9512a94c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258824 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%0ANnsM4|)f9WP^dq=- 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

F9Ck<@RiJoLX=RjXyZyA$)Ipeh4?TNP+Eozyr@& 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

SN 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 diff --git a/DepFiles/unittest/junit-4.12.jar b/DepFiles/unittest/junit-4.12.jar deleted file mode 100644 index 3a7fc266c3e32283a2b21fe12166ebdcc33a1da1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314932 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

&R7;REBZd0NaNVcN6Eoy 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

+ * This estimate doesn't include the size of the {@link SpatialReference} object + * because instances of {@link SpatialReference} are expected to be shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public long estimateMemorySize() { + long sz = SIZE_OF_MAPGEOMETRY; + if (m_geometry != null) + sz += m_geometry.estimateMemorySize(); + return sz; + } + @Override public int hashCode() { SpatialReference sr = getSpatialReference(); diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java index 085dd397..d9d9d692 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java @@ -49,7 +49,7 @@ public Type getType() { * After that the curves are replaced with straight segments. * @param progressTracker * @return Returns the densified geometries (It does nothing to geometries - * with dim < 1, but simply passes them along). + * with dim < 1, but simply passes them along). */ public abstract GeometryCursor execute(GeometryCursor inputGeometries, double maxLength, ProgressTracker progressTracker); @@ -67,7 +67,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, * After that the curves are replaced with straight segments. * @param progressTracker * @return Returns the densified geometry. (It does nothing to geometries - * with dim < 1, but simply passes them along). + * with dim < 1, but simply passes them along). */ public abstract Geometry execute(Geometry inputGeometry, double maxLength, ProgressTracker progressTracker); diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 87de2d4a..2a25215d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -34,7 +34,7 @@ public Type getType() { * Performs the ImportFromGeoJson operation. * * @param type Use the {@link Geometry.Type} enum. - * @param jsonObject The JSONObject holding the geometry and spatial reference. + * @param jsonReader The JSONReader. * @return Returns the imported MapGeometry. * @throws JsonGeometryException */ @@ -49,7 +49,6 @@ public Type getType() { * @param type Use the {@link Geometry.Type} enum. * @param geoJsonString The string holding the Geometry in geoJson format. * @return Returns the imported MapGeometry. - * @throws JSONException * */ public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker); @@ -61,7 +60,6 @@ public Type getType() { * @param import_flags Use the {@link GeoJsonImportFlags} interface. * @param geoJsonString The string holding the Geometry in geoJson format. * @return Returns the imported MapOGCStructure. - * @throws JSONException */ public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker); diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index ee3b4833..5afc6e37 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -53,12 +53,12 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *@param sr The spatial reference is used to get tolerance value. Can be null, then the tolerance is not used and the operation is performed with *a small tolerance value just enough to make the operation robust. *@param progress_tracker Allows to cancel the operation. Can be null. - *@param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). + *@param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). *The value of -1 means the lower dimension in the intersecting pair. *This is a fastest option when intersecting polygons with polygons or polylines. - *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate + *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate *what dimensions of geometry one wants to be returned. For example, to return - *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. + *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. *@return Returns the cursor of the intersection result. The cursors' getGeometryID method returns the current ID of the input geometry *being processed. When dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. @@ -81,7 +81,7 @@ public abstract GeometryCursor execute(GeometryCursor input_geometries, *points, but the overlaps only). *The call is equivalent to calling the overloaded method using cursors: *execute(new SimpleGeometryCursor(input_geometry), new SimpleGeometryCursor(intersector), sr, progress_tracker, mask).next(); - *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); + *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); *@param inputGeometry is the Geometry instance to be intersected by the intersector. *@param intersector is the intersector Geometry. *@param sr The spatial reference to get the tolerance value from. Can be null, then the tolerance is calculated from the input geometries. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java index 7b4f3673..bdcf5005 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffset.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffset.java @@ -43,7 +43,7 @@ public enum JoinType { * * The offset operation creates a geometry that is a constant distance from * an input polyline or polygon. It is similar to buffering, but produces a - * one sided result. If offsetDistance > 0, then the offset geometry is + * one sided result. If offsetDistance greater than 0, then the offset geometry is * constructed to the right of the oriented input geometry, otherwise it is * constructed to the left. For a simple polygon, the orientation of outer * rings is clockwise and for inner rings it is counter clockwise. So the @@ -82,7 +82,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, * * The offset operation creates a geometry that is a constant distance from * an input polyline or polygon. It is similar to buffering, but produces a - * one sided result. If offsetDistance > 0, then the offset geometry is + * one sided result. If offsetDistance greater than 0, then the offset geometry is * constructed to the right of the oriented input geometry, otherwise it is * constructed to the left. For a simple polygon, the orientation of outer * rings is clockwise and for inner rings it is counter clockwise. So the diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index d96589ae..a421400f 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -39,7 +39,7 @@ public class Point extends Geometry implements Serializable { //We are using writeReplace instead. //private static final long serialVersionUID = 2L; - double[] m_attributes; // use doubles to store everything (long are bitcast) + double[] m_attributes; // use doubles to store everything (long are bitcast) /** * Creates an empty 2D point. @@ -626,22 +626,22 @@ public int hashCode() { return hashCode; } - @Override - public Geometry getBoundary() { - return null; - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - double v = getAttributeAsDbl(semantics, i); - if (Double.isNaN(v)) - setAttribute(semantics, i, value); - } - } + @Override + public Geometry getBoundary() { + return null; + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = getAttributeAsDbl(semantics, i); + if (Double.isNaN(v)) + setAttribute(semantics, i, value); + } + } } diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 245b8156..90cc1e46 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -435,7 +435,7 @@ public boolean isNaN() { } /** - * Calculates the orientation of the triangle formed by p->q->r. Returns 1 + * Calculates the orientation of the triangle formed by p, q, r. Returns 1 * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use * high precision arithmetics for some special degenerate cases. */ diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index a8298077..949a3797 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -145,39 +145,41 @@ public int getExteriorRingCount() { return m_impl.getOGCPolygonCount(); } - public interface FillRule { - /** - * odd-even fill rule. This is the default value. A point is in the polygon interior if a ray - * from this point to infinity crosses odd number of segments of the polygon. - */ - public final static int enumFillRuleOddEven = 0; - /** - * winding fill rule (aka non-zero winding rule). A point is in the polygon interior if a winding number is not zero. - * To compute a winding number for a point, draw a ray from this point to infinity. If N is the number of times the ray - * crosses segments directed up and the M is the number of times it crosses segments directed down, - * then the winding number is equal to N-M. - */ - public final static int enumFillRuleWinding = 1; - }; - - /** - *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. - *Can be use by drawing code to pass around the fill rule of graphic path. - *This property is not persisted in any format yet. - *See also Polygon.FillRule. - */ - public void setFillRule(int rule) { - m_impl.setFillRule(rule); - } - - /** - *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. - *Changing the fill rule on the polygon that has no self intersections has no physical effect. - *Can be use by drawing code to pass around the fill rule of graphic path. - *This property is not persisted in any format yet. - *See also Polygon.FillRule. - */ - public int getFillRule() { - return m_impl.getFillRule(); - } + public interface FillRule { + /** + * odd-even fill rule. This is the default value. A point is in the polygon + * interior if a ray from this point to infinity crosses odd number of segments + * of the polygon. + */ + public final static int enumFillRuleOddEven = 0; + /** + * winding fill rule (aka non-zero winding rule). A point is in the polygon + * interior if a winding number is not zero. To compute a winding number for a + * point, draw a ray from this point to infinity. If N is the number of times + * the ray crosses segments directed up and the M is the number of times it + * crosses segments directed down, then the winding number is equal to N-M. + */ + public final static int enumFillRuleWinding = 1; + }; + + /** + * Fill rule for the polygon that defines the interior of the self intersecting + * polygon. It affects the Simplify operation. Can be use by drawing code to + * pass around the fill rule of graphic path. This property is not persisted in + * any format yet. See also Polygon.FillRule. + */ + public void setFillRule(int rule) { + m_impl.setFillRule(rule); + } + + /** + * Fill rule for the polygon that defines the interior of the self intersecting + * polygon. It affects the Simplify operation. Changing the fill rule on the + * polygon that has no self intersections has no physical effect. Can be use by + * drawing code to pass around the fill rule of graphic path. This property is + * not persisted in any format yet. See also Polygon.FillRule. + */ + public int getFillRule() { + return m_impl.getFillRule(); + } } diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 0d842806..9f45d7ea 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 86683a93..31460366 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -21,6 +21,7 @@ email: contracts@esri.com */ + package com.esri.core.geometry; import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; @@ -38,90 +39,83 @@ import static sun.misc.Unsafe.ARRAY_SHORT_BASE_OFFSET; import static sun.misc.Unsafe.ARRAY_SHORT_INDEX_SCALE; -public final class SizeOf -{ - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT = 24; +public final class SizeOf { + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_DBL = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_DBL = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 = 24; - public static final int SIZE_OF_ENVELOPE = 32; + public static final int SIZE_OF_ENVELOPE = 32; - public static final int SIZE_OF_ENVELOPE2D = 48; + public static final int SIZE_OF_ENVELOPE2D = 48; - public static final int SIZE_OF_LINE = 56; + public static final int SIZE_OF_LINE = 56; - public static final int SIZE_OF_MULTI_PATH = 24; + public static final int SIZE_OF_MULTI_PATH = 24; - public static final int SIZE_OF_MULTI_PATH_IMPL = 112; + public static final int SIZE_OF_MULTI_PATH_IMPL = 112; - public static final int SIZE_OF_MULTI_POINT = 24; + public static final int SIZE_OF_MULTI_POINT = 24; - public static final int SIZE_OF_MULTI_POINT_IMPL = 56; + public static final int SIZE_OF_MULTI_POINT_IMPL = 56; - public static final int SIZE_OF_POINT = 24; + public static final int SIZE_OF_POINT = 24; - public static final int SIZE_OF_POLYGON = 24; + public static final int SIZE_OF_POLYGON = 24; - public static final int SIZE_OF_POLYLINE = 24; + public static final int SIZE_OF_POLYLINE = 24; - public static final int SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION = 24; + public static final int SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION = 24; - public static final int SIZE_OF_OGC_LINE_STRING = 24; + public static final int SIZE_OF_OGC_LINE_STRING = 24; - public static final int SIZE_OF_OGC_MULTI_LINE_STRING = 24; + public static final int SIZE_OF_OGC_MULTI_LINE_STRING = 24; - public static final int SIZE_OF_OGC_MULTI_POINT = 24; + public static final int SIZE_OF_OGC_MULTI_POINT = 24; - public static final int SIZE_OF_OGC_MULTI_POLYGON = 24; + public static final int SIZE_OF_OGC_MULTI_POLYGON = 24; - public static final int SIZE_OF_OGC_POINT = 24; + public static final int SIZE_OF_OGC_POINT = 24; - public static final int SIZE_OF_OGC_POLYGON = 24; + public static final int SIZE_OF_OGC_POLYGON = 24; + + public static final int SIZE_OF_MAPGEOMETRY = 24; - public static long sizeOfByteArray(int length) - { - return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); - } + public static long sizeOfByteArray(int length) { + return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); + } - public static long sizeOfShortArray(int length) - { - return ARRAY_SHORT_BASE_OFFSET + (((long) ARRAY_SHORT_INDEX_SCALE) * length); - } + public static long sizeOfShortArray(int length) { + return ARRAY_SHORT_BASE_OFFSET + (((long) ARRAY_SHORT_INDEX_SCALE) * length); + } - public static long sizeOfCharArray(int length) - { - return ARRAY_CHAR_BASE_OFFSET + (((long) ARRAY_CHAR_INDEX_SCALE) * length); - } + public static long sizeOfCharArray(int length) { + return ARRAY_CHAR_BASE_OFFSET + (((long) ARRAY_CHAR_INDEX_SCALE) * length); + } - public static long sizeOfIntArray(int length) - { - return ARRAY_INT_BASE_OFFSET + (((long) ARRAY_INT_INDEX_SCALE) * length); - } + public static long sizeOfIntArray(int length) { + return ARRAY_INT_BASE_OFFSET + (((long) ARRAY_INT_INDEX_SCALE) * length); + } - public static long sizeOfLongArray(int length) - { - return ARRAY_LONG_BASE_OFFSET + (((long) ARRAY_LONG_INDEX_SCALE) * length); - } + public static long sizeOfLongArray(int length) { + return ARRAY_LONG_BASE_OFFSET + (((long) ARRAY_LONG_INDEX_SCALE) * length); + } - public static long sizeOfFloatArray(int length) - { - return ARRAY_FLOAT_BASE_OFFSET + (((long) ARRAY_FLOAT_INDEX_SCALE) * length); - } + public static long sizeOfFloatArray(int length) { + return ARRAY_FLOAT_BASE_OFFSET + (((long) ARRAY_FLOAT_INDEX_SCALE) * length); + } - public static long sizeOfDoubleArray(int length) - { - return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); - } + public static long sizeOfDoubleArray(int length) { + return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); + } - private SizeOf() - { - } + private SizeOf() { + } } diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 22b0c74f..4c337e27 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 4ad748be..a3f60a6d 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index ba516b5f..64d2bfc2 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -15,92 +15,81 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class TestEstimateMemorySize -{ - @Test - public void testInstanceSizes() - { - assertEquals(getInstanceSize(AttributeStreamOfFloat.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT); - assertEquals(getInstanceSize(AttributeStreamOfDbl.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL); - assertEquals(getInstanceSize(AttributeStreamOfInt8.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8); - assertEquals(getInstanceSize(AttributeStreamOfInt16.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16); - assertEquals(getInstanceSize(AttributeStreamOfInt32.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32); - assertEquals(getInstanceSize(AttributeStreamOfInt64.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64); - assertEquals(getInstanceSize(Envelope.class), SizeOf.SIZE_OF_ENVELOPE); - assertEquals(getInstanceSize(Envelope2D.class), SizeOf.SIZE_OF_ENVELOPE2D); - assertEquals(getInstanceSize(Line.class), SizeOf.SIZE_OF_LINE); - assertEquals(getInstanceSize(MultiPath.class), SizeOf.SIZE_OF_MULTI_PATH); - assertEquals(getInstanceSize(MultiPathImpl.class), SizeOf.SIZE_OF_MULTI_PATH_IMPL); - assertEquals(getInstanceSize(MultiPoint.class), SizeOf.SIZE_OF_MULTI_POINT); - assertEquals(getInstanceSize(MultiPointImpl.class), SizeOf.SIZE_OF_MULTI_POINT_IMPL); - assertEquals(getInstanceSize(Point.class), SizeOf.SIZE_OF_POINT); - assertEquals(getInstanceSize(Polygon.class), SizeOf.SIZE_OF_POLYGON); - assertEquals(getInstanceSize(Polyline.class), SizeOf.SIZE_OF_POLYLINE); - assertEquals(getInstanceSize(OGCConcreteGeometryCollection.class), SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION); - assertEquals(getInstanceSize(OGCLineString.class), SizeOf.SIZE_OF_OGC_LINE_STRING); - assertEquals(getInstanceSize(OGCMultiLineString.class), SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING); - assertEquals(getInstanceSize(OGCMultiPoint.class), SizeOf.SIZE_OF_OGC_MULTI_POINT); - assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); - assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); - assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); - } +public class TestEstimateMemorySize { + @Test + public void testInstanceSizes() { + assertEquals(getInstanceSize(AttributeStreamOfFloat.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT); + assertEquals(getInstanceSize(AttributeStreamOfDbl.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL); + assertEquals(getInstanceSize(AttributeStreamOfInt8.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8); + assertEquals(getInstanceSize(AttributeStreamOfInt16.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16); + assertEquals(getInstanceSize(AttributeStreamOfInt32.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32); + assertEquals(getInstanceSize(AttributeStreamOfInt64.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64); + assertEquals(getInstanceSize(Envelope.class), SizeOf.SIZE_OF_ENVELOPE); + assertEquals(getInstanceSize(Envelope2D.class), SizeOf.SIZE_OF_ENVELOPE2D); + assertEquals(getInstanceSize(Line.class), SizeOf.SIZE_OF_LINE); + assertEquals(getInstanceSize(MultiPath.class), SizeOf.SIZE_OF_MULTI_PATH); + assertEquals(getInstanceSize(MultiPathImpl.class), SizeOf.SIZE_OF_MULTI_PATH_IMPL); + assertEquals(getInstanceSize(MultiPoint.class), SizeOf.SIZE_OF_MULTI_POINT); + assertEquals(getInstanceSize(MultiPointImpl.class), SizeOf.SIZE_OF_MULTI_POINT_IMPL); + assertEquals(getInstanceSize(Point.class), SizeOf.SIZE_OF_POINT); + assertEquals(getInstanceSize(Polygon.class), SizeOf.SIZE_OF_POLYGON); + assertEquals(getInstanceSize(Polyline.class), SizeOf.SIZE_OF_POLYLINE); + assertEquals(getInstanceSize(OGCConcreteGeometryCollection.class), + SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION); + assertEquals(getInstanceSize(OGCLineString.class), SizeOf.SIZE_OF_OGC_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiLineString.class), SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiPoint.class), SizeOf.SIZE_OF_OGC_MULTI_POINT); + assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); + assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); + assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); + } - private static int getInstanceSize(Class clazz) - { - return ClassLayout.parseClass(clazz).instanceSize(); - } + private static long getInstanceSize(Class clazz) { + return ClassLayout.parseClass(clazz).instanceSize(); + } - @Test - public void testPoint() - { - testGeometry(parseWkt("POINT (1 2)")); - } + @Test + public void testPoint() { + testGeometry(parseWkt("POINT (1 2)")); + } - @Test - public void testMultiPoint() - { - testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); - } + @Test + public void testMultiPoint() { + testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); + } - @Test - public void testLineString() - { - testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); - } + @Test + public void testLineString() { + testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); + } - @Test - public void testMultiLineString() - { - testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); - } + @Test + public void testMultiLineString() { + testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); + } - @Test - public void testPolygon() - { - testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); - } + @Test + public void testPolygon() { + testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); + } - @Test - public void testMultiPolygon() - { - testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); - } + @Test + public void testMultiPolygon() { + testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); + } - @Test - public void testGeometryCollection() - { - testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); - } + @Test + public void testGeometryCollection() { + testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); + } - private void testGeometry(OGCGeometry geometry) - { - assertTrue(geometry.estimateMemorySize() > 0); - } + private void testGeometry(OGCGeometry geometry) { + assertTrue(geometry.estimateMemorySize() > 0); + } - private static OGCGeometry parseWkt(String wkt) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - geometry.setSpatialReference(null); - return geometry; - } + private static OGCGeometry parseWkt(String wkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + geometry.setSpatialReference(null); + return geometry; + } } From b3d9cc30cb68c883d3155eaa3271eaa468c4a195 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Fri, 16 Mar 2018 14:53:43 -0700 Subject: [PATCH 051/116] Add license header. --- .../core/geometry/TestEstimateMemorySize.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index 64d2bfc2..e4195c58 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -1,4 +1,28 @@ -package com.esri.core.geometry; +/* + Copyright 1995-2018 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + package com.esri.core.geometry; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import com.esri.core.geometry.ogc.OGCGeometry; From c733591b114d2d55fdd4646331f10ba0d8ca47d2 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 19 Mar 2018 11:40:45 -0700 Subject: [PATCH 052/116] Geometry release v2.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d55f9fcf..2c6e2580 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.0.0 + 2.1.0 jar Esri Geometry API for Java From 517039c635cd07acbb61aa99d260855da23f531e Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Tue, 20 Mar 2018 09:50:15 -0700 Subject: [PATCH 053/116] README: v2.1.0 ; POM: v2.2 development (#161) --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34589c2f..bcc099fe 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.0.0 + 2.1.0 ``` diff --git a/pom.xml b/pom.xml index 2c6e2580..0255ec18 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.1.0 + 2.2.0-SNAPSHOT jar Esri Geometry API for Java From 5602a2a5d5266fc7cae7548b456a2d16cd1c9af9 Mon Sep 17 00:00:00 2001 From: danio Date: Thu, 29 Mar 2018 21:22:59 +0100 Subject: [PATCH 054/116] Test union operator with some geometry (#165) @danio Looks good. Thank you for the tests! --- .../com/esri/core/geometry/TestUnion.java | 129 +++++++++++++++++- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index 55392e39..77032413 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -40,11 +40,6 @@ protected void tearDown() throws Exception { @Test public static void testUnion() { - Point pt = new Point(10, 20); - - Point pt2 = new Point(); - pt2.setXY(10, 10); - Envelope env1 = new Envelope(10, 10, 30, 50); Envelope env2 = new Envelope(30, 10, 60, 50); Geometry[] geomArray = new Geometry[] { env1, env2 }; @@ -57,5 +52,129 @@ public static void testUnion() { GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(1, path.getPathCount()); + assertEquals(6, path.getPathEnd(0)); + assertEquals(new Point2D(10, 10), path.getXY(0)); + assertEquals(new Point2D(10, 50), path.getXY(1)); + assertEquals(new Point2D(30, 50), path.getXY(2)); + assertEquals(new Point2D(60, 50), path.getXY(3)); + assertEquals(new Point2D(60, 10), path.getXY(4)); + assertEquals(new Point2D(30, 10), path.getXY(5)); + } + + @Test + public static void testUnionDistinctGeometries() { + Envelope env = new Envelope(1, 5, 3, 10); + + Polygon polygon = new Polygon(); + polygon.startPath(new Point(4, 3)); + polygon.lineTo(new Point(7, 6)); + polygon.lineTo(new Point(6, 8)); + polygon.lineTo(new Point(4, 3)); + + Geometry[] geomArray = new Geometry[] { env, polygon }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(2, path.getPathCount()); + + assertEquals(3, path.getPathEnd(0)); + assertEquals(7, path.getPathEnd(1)); + // from polygon + assertEquals(new Point2D(4, 3), path.getXY(0)); + assertEquals(new Point2D(6, 8), path.getXY(1)); + assertEquals(new Point2D(7, 6), path.getXY(2)); + // from envelope + assertEquals(new Point2D(1, 5), path.getXY(3)); + assertEquals(new Point2D(1, 10), path.getXY(4)); + assertEquals(new Point2D(3, 10), path.getXY(5)); + assertEquals(new Point2D(3, 5), path.getXY(6)); + } + + @Test + public static void testUnionCoincidentPolygons() { + Polygon polygon1 = new Polygon(); + polygon1.startPath(new Point(3, 2)); + polygon1.lineTo(new Point(1, 2)); + polygon1.lineTo(new Point(1, 4)); + polygon1.lineTo(new Point(3, 4)); + polygon1.lineTo(new Point(3, 2)); + + Polygon polygon2 = new Polygon(); + polygon2.startPath(new Point(1, 2)); + polygon2.lineTo(new Point(1, 4)); + polygon2.lineTo(new Point(3, 4)); + polygon2.lineTo(new Point(3, 2)); + polygon2.lineTo(new Point(1, 2)); + + Geometry[] geomArray = new Geometry[] { polygon1, polygon2 }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(1, path.getPathCount()); + assertEquals(4, path.getPathEnd(0)); + assertEquals(new Point2D(1, 2), path.getXY(0)); + assertEquals(new Point2D(1, 4), path.getXY(1)); + assertEquals(new Point2D(3, 4), path.getXY(2)); + assertEquals(new Point2D(3, 2), path.getXY(3)); + } + + @Test + public static void testUnionCoincidentPolygonsWithReverseWinding() { + // Input polygons have CCW winding, result is always CW + Polygon polygon1 = new Polygon(); + polygon1.startPath(new Point(3, 2)); + polygon1.lineTo(new Point(3, 4)); + polygon1.lineTo(new Point(1, 4)); + polygon1.lineTo(new Point(1, 2)); + polygon1.lineTo(new Point(3, 2)); + + Polygon polygon2 = new Polygon(); + polygon2.startPath(new Point(1, 2)); + polygon2.lineTo(new Point(3, 2)); + polygon2.lineTo(new Point(3, 4)); + polygon2.lineTo(new Point(1, 4)); + polygon2.lineTo(new Point(1, 2)); + + Polygon expectedPolygon = new Polygon(); + expectedPolygon.startPath(new Point(1, 2)); + expectedPolygon.lineTo(new Point(1, 4)); + expectedPolygon.lineTo(new Point(3, 4)); + expectedPolygon.lineTo(new Point(3, 2)); + expectedPolygon.lineTo(new Point(1, 2)); + + Geometry[] geomArray = new Geometry[] { polygon1, polygon2 }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(1, path.getPathCount()); + assertEquals(4, path.getPathEnd(0)); + assertEquals(new Point2D(1, 2), path.getXY(0)); + assertEquals(new Point2D(1, 4), path.getXY(1)); + assertEquals(new Point2D(3, 4), path.getXY(2)); + assertEquals(new Point2D(3, 2), path.getXY(3)); } } From e631c8aadcc23d6dfdcfc6ec38668d7079bda522 Mon Sep 17 00:00:00 2001 From: danio Date: Thu, 29 Mar 2018 23:04:10 +0100 Subject: [PATCH 055/116] Ant doesn't work for building (#163) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bcc099fe..46b42a05 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ The Esri Geometry API for Java can be used to enable spatial data processing in Building the source: 1. Download and unzip the .zip file, or clone the repository. -2. Deploy the esri-geometry-api.jar to the target system, add a reference to it in a Java project. -3. To build the jar, Javadoc, and run the unit-tests, run the “ant” command-line command from within the cloned directory. The ant tool runs the “build.xml” script which creates the jar, runs the unit tests, then creates the Javadoc documentation files. +1. To build the jar, run the `mvn compile` command-line command from within the cloned directory. +1. Deploy the esri-geometry-api.jar to the target system, add a reference to it in a Java project. +1. To run the unit-tests, run the `mvn test` command-line command from within the cloned directory. The project is also available as a [Maven](http://maven.apache.org/) dependency: @@ -30,7 +31,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: ## Requirements * Java JDK 1.6 or greater. -* [Apache Ant](http://ant.apache.org/) build system. +* [Apache Maven](https://maven.apache.org/) build system. * Experience developing MapReduce applications for [Apache Hadoop](http://hadoop.apache.org/). * Familiarity with text-based spatial data formats such as JSON or WKT would be useful. From 27adbbbb8e5bd54d8248dcb681347cfaec285994 Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 9 Apr 2018 19:26:20 -0400 Subject: [PATCH 056/116] Add OGCGeometry#centroid operation (#169) @mbasmanova Thank you! --- .../java/com/esri/core/geometry/Operator.java | 2 +- .../core/geometry/OperatorCentroid2D.java | 40 +++++ .../geometry/OperatorCentroid2DLocal.java | 164 ++++++++++++++++++ .../core/geometry/OperatorFactoryLocal.java | 3 +- .../esri/core/geometry/ogc/OGCGeometry.java | 13 ++ .../core/geometry/ogc/OGCMultiSurface.java | 5 - .../esri/core/geometry/ogc/OGCSurface.java | 5 - .../com/esri/core/geometry/TestCentroid.java | 115 ++++++++++++ .../esri/core/geometry/TestOGCCentroid.java | 90 ++++++++++ 9 files changed, 425 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/OperatorCentroid2D.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java create mode 100644 src/test/java/com/esri/core/geometry/TestCentroid.java create mode 100644 src/test/java/com/esri/core/geometry/TestOGCCentroid.java diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 9dcd804d..6866fb16 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -40,7 +40,7 @@ public enum Type { Union, Difference, - Proximity2D, + Proximity2D, Centroid2D, Relate, Equals, Disjoint, Intersects, Within, Contains, Crosses, Touches, Overlaps, diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java new file mode 100644 index 00000000..f44a21f8 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public abstract class OperatorCentroid2D extends Operator +{ + @Override + public Type getType() + { + return Type.Centroid2D; + } + + public abstract Point2D execute(Geometry geometry, ProgressTracker progressTracker); + + public static OperatorCentroid2D local() + { + return (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Type.Centroid2D); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java new file mode 100644 index 00000000..91b7c948 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java @@ -0,0 +1,164 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import static java.lang.Math.sqrt; + +public class OperatorCentroid2DLocal extends OperatorCentroid2D +{ + @Override + public Point2D execute(Geometry geometry, ProgressTracker progressTracker) + { + if (geometry.isEmpty()) { + return null; + } + + Geometry.Type geometryType = geometry.getType(); + switch (geometryType) { + case Point: + return ((Point) geometry).getXY(); + case Line: + return computeLineCentroid((Line) geometry); + case Envelope: + return ((Envelope) geometry).getCenterXY(); + case MultiPoint: + return computePointsCentroid((MultiPoint) geometry); + case Polyline: + return computePolylineCentroid(((Polyline) geometry)); + case Polygon: + return computePolygonCentroid((Polygon) geometry); + default: + throw new UnsupportedOperationException("Unexpected geometry type: " + geometryType); + } + } + + private static Point2D computeLineCentroid(Line line) + { + return new Point2D((line.getEndX() - line.getStartX()) / 2, (line.getEndY() - line.getStartY()) / 2); + } + + // Points centroid is arithmetic mean of the input points + private static Point2D computePointsCentroid(MultiPoint multiPoint) + { + double xSum = 0; + double ySum = 0; + int pointCount = multiPoint.getPointCount(); + Point2D point2D = new Point2D(); + for (int i = 0; i < pointCount; i++) { + multiPoint.getXY(i, point2D); + xSum += point2D.x; + ySum += point2D.y; + } + return new Point2D(xSum / pointCount, ySum / pointCount); + } + + // Lines centroid is weighted mean of each line segment, weight in terms of line length + private static Point2D computePolylineCentroid(Polyline polyline) + { + double xSum = 0; + double ySum = 0; + double weightSum = 0; + + Point2D startPoint = new Point2D(); + Point2D endPoint = new Point2D(); + for (int i = 0; i < polyline.getPathCount(); i++) { + polyline.getXY(polyline.getPathStart(i), startPoint); + polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); + double dx = endPoint.x - startPoint.x; + double dy = endPoint.y - startPoint.y; + double length = sqrt(dx * dx + dy * dy); + weightSum += length; + xSum += (startPoint.x + endPoint.x) * length / 2; + ySum += (startPoint.y + endPoint.y) * length / 2; + } + return new Point2D(xSum / weightSum, ySum / weightSum); + } + + // Polygon centroid: area weighted average of centroids in case of holes + private static Point2D computePolygonCentroid(Polygon polygon) + { + int pathCount = polygon.getPathCount(); + + if (pathCount == 1) { + return getPolygonSansHolesCentroid(polygon); + } + + double xSum = 0; + double ySum = 0; + double areaSum = 0; + + for (int i = 0; i < pathCount; i++) { + int startIndex = polygon.getPathStart(i); + int endIndex = polygon.getPathEnd(i); + + Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); + + Point2D centroid = getPolygonSansHolesCentroid(sansHoles); + double area = sansHoles.calculateArea2D(); + + xSum += centroid.x * area; + ySum += centroid.y * area; + areaSum += area; + } + + return new Point2D(xSum / areaSum, ySum / areaSum); + } + + private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) + { + Polyline boundary = new Polyline(); + boundary.startPath(polygon.getPoint(startIndex)); + for (int i = startIndex + 1; i < endIndex; i++) { + Point current = polygon.getPoint(i); + boundary.lineTo(current); + } + + final Polygon newPolygon = new Polygon(); + newPolygon.add(boundary, false); + return newPolygon; + } + + // Polygon sans holes centroid: + // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) + // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) + private static Point2D getPolygonSansHolesCentroid(Polygon polygon) + { + int pointCount = polygon.getPointCount(); + double xSum = 0; + double ySum = 0; + double signedArea = 0; + + Point2D current = new Point2D(); + Point2D next = new Point2D(); + for (int i = 0; i < pointCount; i++) { + polygon.getXY(i, current); + polygon.getXY((i + 1) % pointCount, next); + double ladder = current.x * next.y - next.x * current.y; + xSum += (current.x + next.x) * ladder; + ySum += (current.y + next.y) * ladder; + signedArea += ladder / 2; + } + return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 727b4a69..153b4728 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -29,7 +29,6 @@ import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; @@ -61,6 +60,8 @@ public class OperatorFactoryLocal extends OperatorFactory { st_supportedOperators.put(Type.Proximity2D, new OperatorProximity2DLocal()); + st_supportedOperators.put(Type.Centroid2D, + new OperatorCentroid2DLocal()); st_supportedOperators.put(Type.DensifyByLength, new OperatorDensifyByLengthLocal()); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index a3f60a6d..30d11f67 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -38,6 +38,7 @@ import com.esri.core.geometry.OGCStructure; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorBuffer; +import com.esri.core.geometry.OperatorCentroid2D; import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.OperatorExportToGeoJson; import com.esri.core.geometry.OperatorExportToWkb; @@ -51,6 +52,7 @@ import com.esri.core.geometry.OperatorSimplifyOGC; import com.esri.core.geometry.OperatorUnion; import com.esri.core.geometry.Point; +import com.esri.core.geometry.Point2D; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SimpleGeometryCursor; @@ -427,6 +429,17 @@ public OGCGeometry buffer(double distance) { return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); } + public OGCGeometry centroid() { + OperatorCentroid2D op = (OperatorCentroid2D) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Centroid2D); + + Point2D centroid = op.execute(getEsriGeometry(), null); + if (centroid == null) { + return OGCGeometry.createFromEsriGeometry(new Point(), esriSR); + } + return OGCGeometry.createFromEsriGeometry(new Point(centroid), esriSR); + } + public OGCGeometry convexHull() { com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ConvexHull); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java index 1c98be92..5a36dd0e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java @@ -29,11 +29,6 @@ public double area() { return getEsriGeometry().calculateArea2D(); } - public OGCPoint centroid() { - // TODO - throw new UnsupportedOperationException(); - } - public OGCPoint pointOnSurface() { // TODO throw new UnsupportedOperationException(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java index 43d192e6..989dfec8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java @@ -29,11 +29,6 @@ public double area() { return getEsriGeometry().calculateArea2D(); } - public OGCPoint centroid() { - // TODO: implement me; - throw new UnsupportedOperationException(); - } - public OGCPoint pointOnSurface() { // TODO: support this (need to port OperatorLabelPoint) throw new UnsupportedOperationException(); diff --git a/src/test/java/com/esri/core/geometry/TestCentroid.java b/src/test/java/com/esri/core/geometry/TestCentroid.java new file mode 100644 index 00000000..58c430fb --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestCentroid.java @@ -0,0 +1,115 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.junit.Assert; +import org.junit.Test; + +public class TestCentroid +{ + @Test + public void testPoint() + { + assertCentroid(new Point(1, 2), new Point2D(1, 2)); + } + + @Test + public void testLine() + { + assertCentroid(new Line(0, 0, 10, 20), new Point2D(5, 10)); + } + + @Test + public void testEnvelope() + { + assertCentroid(new Envelope(1, 2, 3,4), new Point2D(2, 3)); + assertCentroid(new Envelope(), null); + } + + @Test + public void testMultiPoint() + { + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(1, 2); + multiPoint.add(3, 1); + multiPoint.add(0, 1); + + assertCentroid(multiPoint, new Point2D(1, 1)); + assertCentroid(new MultiPoint(), null); + } + + @Test + public void testPolyline() + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 2); + polyline.lineTo(3, 4); + assertCentroid(polyline, new Point2D(1.5, 2)); + + polyline.startPath(1, -1); + polyline.lineTo(2, 0); + polyline.lineTo(10, 1); + assertCentroid(polyline, new Point2D(4.093485180902371 , 0.7032574095488145)); + + assertCentroid(new Polyline(), null); + } + + @Test + public void testPolygon() + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(1, 2); + polygon.lineTo(3, 4); + polygon.lineTo(5, 2); + polygon.lineTo(0, 0); + assertCentroid(polygon, new Point2D(2.5, 2)); + + // add a hole + polygon.startPath(2, 2); + polygon.lineTo(2.3, 2); + polygon.lineTo(2.3, 2.4); + polygon.lineTo(2, 2); + assertCentroid(polygon, new Point2D(2.5022670025188916 , 1.9989924433249369)); + + // add another polygon + polygon.startPath(-1, -1); + polygon.lineTo(3, -1); + polygon.lineTo(0.5, -2); + polygon.lineTo(-1, -1); + assertCentroid(polygon, new Point2D(2.166465459423206 , 1.3285043594902748)); + + assertCentroid(new Polygon(), null); + } + + private static void assertCentroid(Geometry geometry, Point2D expectedCentroid) + { + OperatorCentroid2D operator = (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Centroid2D); + + Point2D actualCentroid = operator.execute(geometry, null); + Assert.assertEquals(expectedCentroid, actualCentroid); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java new file mode 100644 index 00000000..bf183bb9 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java @@ -0,0 +1,90 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCPoint; +import org.junit.Assert; +import org.junit.Test; + +public class TestOGCCentroid +{ + @Test + public void testPoint() + { + assertCentroid("POINT (1 2)", new Point(1, 2)); + assertEmptyCentroid("POINT EMPTY"); + } + + @Test + public void testLineString() + { + assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); + assertEmptyCentroid("LINESTRING EMPTY"); + } + + @Test + public void testPolygon() + { + assertCentroid("POLYGON ((1 1, 1 4, 4 4, 4 1))'", new Point(2.5, 2.5)); + assertCentroid("POLYGON ((1 1, 5 1, 3 4))", new Point(3, 2)); + assertCentroid("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))", new Point(2.5416666666666665, 2.5416666666666665)); + assertEmptyCentroid("POLYGON EMPTY"); + } + + @Test + public void testMultiPoint() + { + assertCentroid("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", new Point(2.5, 5)); + assertEmptyCentroid("MULTIPOINT EMPTY"); + } + + @Test + public void testMultiLineString() + { + assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); + assertEmptyCentroid("MULTILINESTRING EMPTY"); + } + + @Test + public void testMultiPolygon() + { + assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", new Point (3.3333333333333335,4)); + assertEmptyCentroid("MULTIPOLYGON EMPTY"); + } + + private static void assertCentroid(String wkt, Point expectedCentroid) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); + } + + private static void assertEmptyCentroid(String wkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(new Point(), geometry.getEsriSpatialReference())); + } +} From 2407d0b1e3149445f7cee7abe9761fe0109c51d4 Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 9 Apr 2018 20:12:38 -0400 Subject: [PATCH 057/116] Fix Envelope#intersect when other is empty (#168) Thanks! --- .../com/esri/core/geometry/Envelope2D.java | 4 +- .../com/esri/core/geometry/TestEnvelope.java | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/esri/core/geometry/TestEnvelope.java diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 8e44dd33..79433dd7 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -321,8 +321,10 @@ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double y * envelope to empty state and returns False. */ public boolean intersect(Envelope2D other) { - if (isEmpty() || other.isEmpty()) + if (isEmpty() || other.isEmpty()) { + setEmpty(); return false; + } if (other.xmin > xmin) xmin = other.xmin; diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope.java b/src/test/java/com/esri/core/geometry/TestEnvelope.java new file mode 100644 index 00000000..56edd466 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestEnvelope.java @@ -0,0 +1,49 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestEnvelope +{ + @Test + public void testIntersect() + { + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(1, 1, 6, 6), new Envelope(1, 1, 5, 5)); + assertIntersection(new Envelope(1, 2, 3, 4), new Envelope(0, 0, 2, 3), new Envelope(1, 2, 2, 3)); + + assertNoIntersection(new Envelope(), new Envelope()); + assertNoIntersection(new Envelope(0, 0, 5, 5), new Envelope()); + assertNoIntersection(new Envelope(), new Envelope(0, 0, 5, 5)); + } + + private static void assertIntersection(Envelope envelope, Envelope other, Envelope intersection) + { + boolean intersects = envelope.intersect(other); + assertTrue(intersects); + assertEquals(envelope, intersection); + } + + private static void assertNoIntersection(Envelope envelope, Envelope other) + { + boolean intersects = envelope.intersect(other); + assertFalse(intersects); + assertTrue(envelope.isEmpty()); + } +} From f68c2413d6a1849238a71d59741f9667e98a1924 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 20 Apr 2018 09:55:34 -0700 Subject: [PATCH 058/116] Fix a typo in usage of Export flags (#171) WktExportFlags.wktExportPolygon is used instead of WkbExportFlags.wkbExportPolygon. They have same value, so there is no bug yet, but it's better to fix that. --- .../java/com/esri/core/geometry/OperatorExportToWkbLocal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index b87b6a8f..bb3abaad 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -170,7 +170,7 @@ else if (wkbBuffer.capacity() < size) if (!bExportZs && !bExportMs) { type = WkbGeometryType.wkbPolygon; - if ((exportFlags & WktExportFlags.wktExportPolygon) == 0) { + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { wkbBuffer.put(offset, byteOrder); offset += 1; wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); From bbfe1d32f9faece46677c651d6965ba1f24e1e46 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 14 May 2018 14:14:19 -0700 Subject: [PATCH 059/116] Stolstov/issue 172 (#174) --- .../com/esri/core/geometry/ConvexHull.java | 8 ++- .../ogc/OGCConcreteGeometryCollection.java | 61 +++++++++++++++++ .../com/esri/core/geometry/ogc/OGCCurve.java | 3 + .../esri/core/geometry/ogc/OGCGeometry.java | 16 ++--- .../esri/core/geometry/ogc/OGCLineString.java | 3 + .../esri/core/geometry/TestConvexHull.java | 66 +++++++++++++++++++ .../java/com/esri/core/geometry/TestOGC.java | 30 +++++++++ 7 files changed, 177 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index ab4b89c9..8e47bb86 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -52,10 +52,13 @@ private ConvexHull(Point2D[] points, int n) { /** * Adds a geometry to the current bounding geometry using an incremental algorithm for dynamic insertion. - * \param geometry The geometry to add to the bounding geometry. + * @param geometry The geometry to add to the bounding geometry. */ void addGeometry(Geometry geometry) { + if (geometry.isEmpty()) + return; + int type = geometry.getType().value(); if (MultiVertexGeometry.isMultiVertex(type)) @@ -80,6 +83,9 @@ Geometry getBoundingGeometry() { Point point = new Point(); int first = m_tree_hull.getFirst(-1); Polygon hull = new Polygon(m_shape.getVertexDescription()); + if (m_tree_hull.size(-1) == 0) + return hull; + m_shape.queryPoint(m_tree_hull.getElement(first), point); hull.startPath(point); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 51e171e7..ce48b82a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -27,11 +27,19 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryException; +import com.esri.core.geometry.MultiPath; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.MultiVertexGeometry; import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.SimpleGeometryCursor; import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.VertexDescription; import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.Point; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -377,6 +385,59 @@ public int getGeometryID() { } } + + @Override + public OGCGeometry convexHull() { + GeometryCursor cursor = OperatorConvexHull.local().execute( + getEsriGeometryCursor(), false, null); + MultiPoint mp = new MultiPoint(); + Polygon polygon = new Polygon(); + VertexDescription vd = null; + for (Geometry geom = cursor.next(); geom != null; geom = cursor.next()) { + vd = geom.getDescription(); + if (geom.isEmpty()) + continue; + + if (geom.getType() == Geometry.Type.Polygon) { + polygon.add((MultiPath) geom, false); + } + else if (geom.getType() == Geometry.Type.Polyline) { + mp.add((MultiVertexGeometry) geom, 0, -1); + } + else if (geom.getType() == Geometry.Type.Point) { + mp.add((Point) geom); + } + else { + throw new GeometryException("internal error"); + } + } + + Geometry resultGeom = null; + if (!mp.isEmpty()) { + resultGeom = OperatorConvexHull.local().execute(mp, null); + } + + if (!polygon.isEmpty()) { + if (!resultGeom.isEmpty()) { + Geometry[] geoms = { resultGeom, polygon }; + resultGeom = OperatorConvexHull.local().execute( + new SimpleGeometryCursor(geoms), true, null).next(); + } + else { + resultGeom = polygon; + } + } + + if (resultGeom == null) { + Point pt = new Point(); + if (vd != null) + pt.assignVertexDescription(vd); + + return new OGCPoint(pt, getEsriSpatialReference()); + } + + return OGCGeometry.createFromEsriGeometry(resultGeom, getEsriSpatialReference(), false); + } List geometries; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 0755dc2e..dd229e5e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -41,6 +41,9 @@ public boolean isRing() { @Override public OGCGeometry boundary() { + if (isEmpty()) + return new OGCMultiPoint(this.getEsriSpatialReference()); + if (isClosed()) return new OGCMultiPoint(new MultiPoint(getEsriGeometry() .getDescription()), esriSR);// return empty multipoint; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 30d11f67..058a47b0 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -433,18 +433,16 @@ public OGCGeometry centroid() { OperatorCentroid2D op = (OperatorCentroid2D) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.Centroid2D); - Point2D centroid = op.execute(getEsriGeometry(), null); - if (centroid == null) { - return OGCGeometry.createFromEsriGeometry(new Point(), esriSR); - } - return OGCGeometry.createFromEsriGeometry(new Point(centroid), esriSR); + Point2D centroid = op.execute(getEsriGeometry(), null); + if (centroid == null) { + return OGCGeometry.createFromEsriGeometry(new Point(), esriSR); + } + return OGCGeometry.createFromEsriGeometry(new Point(centroid), esriSR); } public OGCGeometry convexHull() { - com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - com.esri.core.geometry.GeometryCursor cursor = op.execute( - getEsriGeometryCursor(), true, null); + com.esri.core.geometry.GeometryCursor cursor = OperatorConvexHull.local().execute( + getEsriGeometryCursor(), false, null); return OGCGeometry.createFromEsriCursor(cursor, esriSR); } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 464b9a7c..8d086c3b 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -82,6 +82,9 @@ public OGCPoint pointN(int n) { @Override public boolean isClosed() { + if (isEmpty()) + return false; + return multiPath.isClosedPathInXYPlane(0); } diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index b2e2d59e..b29d3aaf 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -27,6 +27,8 @@ import junit.framework.TestCase; import org.junit.Test; +import com.esri.core.geometry.ogc.OGCGeometry; + public class TestConvexHull extends TestCase { @Override protected void setUp() throws Exception { @@ -992,5 +994,69 @@ public void testHullTickTock() { assertTrue(p5.x == -5.0 && p5.y == 1.25); assertTrue(p6.x == 0.0 && p6.y == 10.0); } + + @Test + public void testHullIssueGithub172() { + { + //empty + OGCGeometry geom = OGCGeometry.fromText("MULTIPOINT EMPTY"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT EMPTY") == 0); + } + { + //Point + OGCGeometry geom = OGCGeometry.fromText("POINT (1 2)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT (1 2)") == 0); + } + { + //line + OGCGeometry geom = OGCGeometry.fromText("MULTIPOINT (1 1, 2 2)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("LINESTRING (1 1, 2 2)") == 0); + } + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION EMPTY"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT EMPTY") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 2))"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT (1 2)") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT (1 1)") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, LINESTRING (1 1, 2 2), POINT(3 3), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("LINESTRING (1 1, 3 3)") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, LINESTRING (1 1, 2 2), POINT(3 3), LINESTRING EMPTY, POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POLYGON ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))") == 0); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index ca2bcf6d..31263705 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -926,4 +926,34 @@ public void testPolylineSimplifyIssueGithub52() throws Exception { } } + @Test + public void testEmptyBoundary() throws Exception { + { + OGCGeometry g = OGCGeometry.fromText("POINT EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTIPOINT EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("MULTIPOINT EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTIPOINT EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("LINESTRING EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTIPOINT EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("POLYGON EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTILINESTRING EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("MULTIPOLYGON EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTILINESTRING EMPTY") == 0); + } + } + + } From e3e0d6ee3d3c5837e29c2acdb35ede34c0857353 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 27 May 2018 22:50:07 -0700 Subject: [PATCH 060/116] Adding collection handling methods --- .../java/com/esri/core/geometry/Geometry.java | 2 +- .../com/esri/core/geometry/OGCStructure.java | 5 + .../ogc/OGCConcreteGeometryCollection.java | 308 +++++++++++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 17 +- .../esri/core/geometry/ogc/OGCLineString.java | 5 +- .../core/geometry/ogc/OGCMultiLineString.java | 21 +- .../esri/core/geometry/ogc/OGCMultiPoint.java | 4 +- .../core/geometry/ogc/OGCMultiPolygon.java | 10 +- .../com/esri/core/geometry/ogc/OGCPoint.java | 4 +- .../esri/core/geometry/ogc/OGCPolygon.java | 4 +- .../java/com/esri/core/geometry/TestOGC.java | 31 ++ 11 files changed, 390 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 8a71a236..d108d328 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -456,7 +456,7 @@ public static int getDimensionFromType(int type) { * @param type * The integer value from geometry enumeration. You can use the * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a point. + * @return TRUE if the geometry is a point (a Point or a Multipoint). */ public static boolean isPoint(int type) { return (type & 0x20) != 0; diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index 9f0875b9..1a9891d9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -23,8 +23,13 @@ */ package com.esri.core.geometry; +import java.util.ArrayList; import java.util.List; +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCGeometryCollection; + public class OGCStructure { public int m_type; public List m_structures; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index ce48b82a..aa4805f3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -32,13 +32,17 @@ import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.MultiVertexGeometry; import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.OGCStructureInternal; import com.esri.core.geometry.OperatorConvexHull; +import com.esri.core.geometry.OperatorDifference; import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SimpleGeometryCursor; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.VertexDescription; import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.OperatorUnion; import com.esri.core.geometry.Point; import java.nio.ByteBuffer; @@ -49,11 +53,26 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; public class OGCConcreteGeometryCollection extends OGCGeometryCollection { + static public String TYPE = "GeometryCollection"; + + List geometries; + public OGCConcreteGeometryCollection(List geoms, SpatialReference sr) { geometries = geoms; esriSR = sr; } + + public OGCConcreteGeometryCollection(GeometryCursor geoms, + SpatialReference sr) { + List ogcGeoms = new ArrayList(10); + for (Geometry g = geoms.next(); g != null; g = geoms.next()) { + ogcGeoms.add(createFromEsriGeometry(g, sr)); + } + + geometries = ogcGeoms; + esriSR = sr; + } public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { geometries = new ArrayList(1); @@ -112,7 +131,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "GeometryCollection"; + return TYPE; } @Override @@ -275,6 +294,7 @@ public boolean isSimple() { for (int i = 0, n = numGeometries(); i < n; i++) if (!geometryN(i).isSimple()) return false; + return true; } @@ -439,8 +459,6 @@ else if (geom.getType() == Geometry.Type.Point) { return OGCGeometry.createFromEsriGeometry(resultGeom, getEsriSpatialReference(), false); } - List geometries; - @Override public void setSpatialReference(SpatialReference esriSR_) { esriSR = esriSR_; @@ -501,4 +519,288 @@ public int hashCode() { return hash; } + + //Relational operations + @Override + public boolean disjoint(OGCGeometry another) { + if (isEmpty() || another.isEmpty()) + return true; + + if (this == another) + return false; + + //TODO: a simple envelope test + + OGCConcreteGeometryCollection flattened1 = flatten(); + if (flattened1.isEmpty()) + return true; + OGCConcreteGeometryCollection otherCol = new OGCConcreteGeometryCollection(another, esriSR); + OGCConcreteGeometryCollection flattened2 = otherCol.flatten(); + if (flattened2.isEmpty()) + return true; + + for (int i = 0, n1 = flattened1.numGeometries(); i < n1; ++i) { + OGCGeometry g1 = flattened1.geometryN(i); + for (int j = 0, n2 = flattened2.numGeometries(); j < n2; ++j) { + OGCGeometry g2 = flattened2.geometryN(i); + if (!g1.disjoint(g2)) + return false; + } + } + + return true; + } + + @Override + public boolean contains(OGCGeometry another) { + if (isEmpty() || another.isEmpty()) + return true; + if (this == another) + return false; + + //TODO: a simple envelope test + + OGCConcreteGeometryCollection flattened1 = flatten(); + if (flattened1.isEmpty()) + return true; + OGCConcreteGeometryCollection otherCol = new OGCConcreteGeometryCollection(another, esriSR); + OGCConcreteGeometryCollection flattened2 = otherCol.flatten(); + if (flattened2.isEmpty()) + return true; + + for (int i = 0, n2 = flattened2.numGeometries(); i < n2; ++i) { + OGCGeometry g2 = flattened2.geometryN(i); + boolean good = false; + for (int j = 0, n1 = flattened1.numGeometries(); j < n1; ++j) { + OGCGeometry g1 = flattened1.geometryN(i); + if (g1.contains(g2)) { + good = true; + break; + } + } + + if (!good) + return false; + } + + //each geometry of another is contained in a geometry from this. + return true; + } + + /** + * Checks if collection is flattened. + * @return True for the flattened collection. A flattened collection contains up to three non-empty geometries: + * an OGCMultiPoint, an OGCMultiPolygon, and an OGCMultiLineString. + */ + public boolean isFlattened() { + int n = numGeometries(); + if (n > 3) + return false; + + for (int i = 0; i < n; ++i) { + OGCGeometry g = geometryN(i); + if (g.isEmpty()) + return false;//no empty allowed + + String t = g.geometryType(); + if (t != OGCMultiPoint.TYPE && t != OGCMultiPolygon.TYPE && t != OGCMultiLineString.TYPE) + return false; + } + + return true; + } + + /** + * Flattens Geometry Collection. + * The result collection contains up to three geometries: + * an OGCMultiPoint, an OGCMultiPolygon, and an OGCMultilineString. + * @return A flattened Geometry Collection, or self if already flattened. + */ + public OGCConcreteGeometryCollection flatten() { + if (isFlattened()) { + return this; + } + + OGCMultiPoint multiPoint = null; + ArrayList polygons = null; + OGCMultiLineString polyline = null; + GeometryCursor gc = getEsriGeometryCursor(); + for (Geometry g = gc.next(); g != null; g = gc.next()) { + if (g.isEmpty()) + continue; + + Geometry.Type t = g.getType(); + + if (t == Geometry.Type.Point) { + if (multiPoint == null) { + multiPoint = new OGCMultiPoint(esriSR); + } + + ((MultiPoint)multiPoint.getEsriGeometry()).add((Point)g); + continue; + } + + if (t == Geometry.Type.MultiPoint) { + if (multiPoint == null) + multiPoint = new OGCMultiPoint(esriSR); + + ((MultiPoint)multiPoint.getEsriGeometry()).add((MultiPoint)g, 0, -1); + continue; + } + + if (t == Geometry.Type.Polyline) { + if (polyline == null) + polyline = new OGCMultiLineString(esriSR); + + ((MultiPath)polyline.getEsriGeometry()).add((Polyline)g, false); + continue; + } + + if (t == Geometry.Type.Polygon) { + if (polygons == null) + polygons = new ArrayList(); + + polygons.add(g); + continue; + } + + throw new GeometryException("internal error");//what else? + } + + List list = new ArrayList(); + + if (multiPoint != null) + list.add(multiPoint); + + if (polyline != null) + list.add(polyline); + + if (polygons != null) { + GeometryCursor unionedPolygons = OperatorUnion.local().execute(new SimpleGeometryCursor(polygons), esriSR, null); + Geometry g = unionedPolygons.next(); + if (!g.isEmpty()) { + list.add(new OGCMultiPolygon((Polygon)g, esriSR)); + } + + } + + return new OGCConcreteGeometryCollection(list, esriSR); + } + + /** + * Fixes topological overlaps in the GeometryCollecion. + * This is equivalent to union of the geometry collection elements. + * + * @return A geometry collection that is flattened and has no overlapping elements. + */ + public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() { + ArrayList geoms = new ArrayList(); + + //flatten and crack/cluster + GeometryCursor cursor = OGCStructureInternal.prepare_for_ops_(flatten().getEsriGeometryCursor(), esriSR); + for (Geometry g = cursor.next(); g != null; g = cursor.next()) { + geoms.add(g); + } + + //make sure geometries don't overlap + return removeOverlapsHelper_(geoms); + } + + private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms) { + ArrayList result = new ArrayList(); + for (int i = 0; i < geoms.size() - 1; ++i) { + Geometry current = geoms.get(i); + if (current.isEmpty()) + continue; + + for (int j = 1; j < geoms.size(); ++j) { + Geometry subG = geoms.get(j); + current = OperatorDifference.local().execute(current, subG, esriSR, null); + if (current.isEmpty()) + break; + } + + if (current.isEmpty()) + continue; + + result.add(current); + } + + return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(geoms), esriSR); + } + + private static class FlatteningCollectionCursor extends GeometryCursor { + private List m_collections; + private GeometryCursor m_current; + private int m_index; + FlatteningCollectionCursor(List collections) { + m_collections = collections; + m_index = -1; + m_current = null; + } + + @Override + public Geometry next() { + while (m_collections != null) { + if (m_current != null) { + Geometry g = m_current.next(); + if (g == null) { + m_current = null; + continue; + } + + return g; + } + else { + m_index++; + if (m_index < m_collections.size()) { + m_current = m_collections.get(m_index).flatten().getEsriGeometryCursor(); + continue; + } + else { + m_collections = null; + m_index = -1; + } + } + } + + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } + + }; + + //Collectively processes group of geometry collections (intersects all segments and clusters points). + //Flattens collections, removes overlaps. + //Once done, the result collections would work well for topological and relational operations. + private List prepare_for_ops_(List geoms) { + assert(geoms != null && !geoms.isEmpty()); + GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(new FlatteningCollectionCursor(geoms), esriSR); + + ArrayList result = new ArrayList(); + int prevCollectionIndex = -1; + ArrayList list = null; + for (Geometry g = prepared.next(); g != null; g = prepared.next()) { + int c = prepared.getGeometryID(); + if (c != prevCollectionIndex) { + if (list != null) { + result.add(removeOverlapsHelper_(list)); + } + + list = new ArrayList(); + } + + list.add(g); + } + + if (list != null) { + result.add(removeOverlapsHelper_(list)); + } + + return result; + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 058a47b0..a34d7af1 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -295,10 +295,7 @@ public boolean crosses(OGCGeometry another) { } public boolean within(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.within(geom1, geom2, - getEsriSpatialReference()); + return another.contains(this); } public boolean contains(OGCGeometry another) { @@ -456,6 +453,18 @@ public OGCGeometry intersection(OGCGeometry another) { } public OGCGeometry union(OGCGeometry another) { + String thisType = geometryType(); + String anotherType = another.geometryType(); + if (thisType != anotherType || thisType == OGCConcreteGeometryCollection.TYPE) { + //heterogeneous union. + //We make a geometry collection, then process to union parts and remove overlaps. + ArrayList geoms = new ArrayList(); + geoms.add(this); + geoms.add(another); + OGCConcreteGeometryCollection geomCol = new OGCConcreteGeometryCollection(geoms, esriSR); + return geomCol.flattenAndRemoveOverlaps(); + } + OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.Union); GeometryCursorAppend ap = new GeometryCursorAppend( diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 8d086c3b..4df18c15 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -40,7 +40,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_LINE_STRING; public class OGCLineString extends OGCCurve { - + static public String TYPE = "LineString"; + /** * The number of Points in this LineString. */ @@ -120,7 +121,7 @@ public OGCPoint endPoint() { @Override public String geometryType() { - return "LineString"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 8fa020c8..91a6580e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -42,22 +42,31 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING; public class OGCMultiLineString extends OGCMultiCurve { + static public String TYPE = "MultiLineString"; + public OGCMultiLineString(Polyline poly, SpatialReference sr) { polyline = poly; esriSR = sr; } + public OGCMultiLineString(SpatialReference sr) { + polyline = new Polyline(); + esriSR = sr; + } + @Override public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), WktExportFlags.wktExportMultiLineString); } + @Override - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); - } + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.ExportToGeoJson); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + } + @Override public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal @@ -74,7 +83,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "MultiLineString"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index b25a948a..4f300ea3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -40,6 +40,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POINT; public class OGCMultiPoint extends OGCGeometryCollection { + public static String TYPE = "MultiPoint"; + public int numGeometries() { return multiPoint.getPointCount(); } @@ -65,7 +67,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "MultiPoint"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index bed0e114..520cc3f7 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -42,12 +42,18 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POLYGON; public class OGCMultiPolygon extends OGCMultiSurface { - + static public String TYPE = "MultiPolygon"; + public OGCMultiPolygon(Polygon src, SpatialReference sr) { polygon = src; esriSR = sr; } + public OGCMultiPolygon(SpatialReference sr) { + polygon = new Polygon(); + esriSR = sr; + } + @Override public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), @@ -89,7 +95,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "MultiPolygon"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 9db01268..608e809f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -39,6 +39,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POINT; public final class OGCPoint extends OGCGeometry { + public static String TYPE = "Point"; + public OGCPoint(Point pt, SpatialReference sr) { point = pt; esriSR = sr; @@ -76,7 +78,7 @@ public double M() { @Override public String geometryType() { - return "Point"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 6f7a74f2..2bc65935 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -40,6 +40,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POLYGON; public class OGCPolygon extends OGCSurface { + public static String TYPE = "Polygon"; + public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { polygon = new Polygon(); for (int i = exteriorRing, n = src.getPathCount(); i < n; i++) { @@ -109,7 +111,7 @@ public OGCMultiCurve boundary() { @Override public String geometryType() { - return "Polygon"; + return TYPE; } @Override diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 31263705..fd7151ec 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -955,5 +955,36 @@ public void testEmptyBoundary() throws Exception { } } + @Test + public void testUnionPointWithEmptyLineString() { + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "GEOMETRYCOLLECTION (POINT (1 2))"); + } + + @Test + public void testUnionPointWithLinestring() { + assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + } + + @Test + public void testUnionLinestringWithEmptyPolygon() { + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING ((1 2, 3 4)))"); + } + + @Test + public void testUnionLinestringWithPolygon() { + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 1 1, 0 1, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))"); + } + @Test + public void testUnionGeometryCollectionWithGeometryCollection() { + assertUnion("GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))", + "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3), POINT (0.5 0.5), POINT (3 5), LINESTRING (3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 0)))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))"); + } + + private void assertUnion(String leftWkt, String rightWkt, String expectedWkt) { + OGCGeometry union = OGCGeometry.fromText(leftWkt).union(OGCGeometry.fromText(rightWkt)); + assertEquals(expectedWkt, union.asText()); + } } From 91a0bdaa7eef5b87f1c9a7215d6484ced6d517b1 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 27 May 2018 23:19:44 -0700 Subject: [PATCH 061/116] Fixed distance --- .../ogc/OGCConcreteGeometryCollection.java | 21 ++++++++++++-- .../esri/core/geometry/ogc/OGCGeometry.java | 4 +++ .../java/com/esri/core/geometry/TestOGC.java | 28 +++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index aa4805f3..2e931696 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -520,6 +520,21 @@ public int hashCode() { return hash; } + @Override + public double distance(OGCGeometry another) { + double minD = 0; + for (int i = 0, n = numGeometries(); i < n; ++i) { + double d = geometryN(i).distance(another); + if (d < minD) { + minD = d; + if (minD == 0) { + break; + } + } + } + + return minD; + } //Relational operations @Override public boolean disjoint(OGCGeometry another) { @@ -708,12 +723,12 @@ public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() { private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms) { ArrayList result = new ArrayList(); - for (int i = 0; i < geoms.size() - 1; ++i) { + for (int i = 0; i < geoms.size(); ++i) { Geometry current = geoms.get(i); if (current.isEmpty()) continue; - for (int j = 1; j < geoms.size(); ++j) { + for (int j = i + 1; j < geoms.size(); ++j) { Geometry subG = geoms.get(j); current = OperatorDifference.local().execute(current, subG, esriSR, null); if (current.isEmpty()) @@ -726,7 +741,7 @@ private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms result.add(current); } - return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(geoms), esriSR); + return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(result), esriSR); } private static class FlatteningCollectionCursor extends GeometryCursor { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index a34d7af1..158d79ef 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -325,6 +325,10 @@ public boolean relate(OGCGeometry another, String matrix) { // analysis public double distance(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.distance(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.distance(geom1, geom2, diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index fd7151ec..c0a744cc 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -967,7 +967,7 @@ public void testUnionPointWithLinestring() { @Test public void testUnionLinestringWithEmptyPolygon() { - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING ((1 2, 3 4)))"); + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))"); } @Test @@ -980,11 +980,35 @@ public void testUnionLinestringWithPolygon() { public void testUnionGeometryCollectionWithGeometryCollection() { assertUnion("GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3), POINT (0.5 0.5), POINT (3 5), LINESTRING (3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 0)))", - "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))"); + "GEOMETRYCOLLECTION (POINT (3 5), LINESTRING (1 2, 2 3, 3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)))"); } private void assertUnion(String leftWkt, String rightWkt, String expectedWkt) { OGCGeometry union = OGCGeometry.fromText(leftWkt).union(OGCGeometry.fromText(rightWkt)); assertEquals(expectedWkt, union.asText()); } + + @Test + public void testDisjointOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertFalse(ogcGeometry.disjoint(OGCGeometry.fromText("POINT (1 1)"))); + } + + @Test + public void testContainsOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(ogcGeometry.contains(OGCGeometry.fromText("POINT (1 1)"))); + } + + @Test + public void testIntersectsOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(ogcGeometry.intersects(OGCGeometry.fromText("POINT (1 1)"))); + } + + @Test + public void testDistanceOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(ogcGeometry.distance(OGCGeometry.fromText("POINT (1 1)")) == 0); + } } From 13c1fa322441f8a24d89899a5a6d54454a2b467a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 27 May 2018 23:33:48 -0700 Subject: [PATCH 062/116] added missing file --- .../core/geometry/OGCStructureInternal.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/java/com/esri/core/geometry/OGCStructureInternal.java diff --git a/src/main/java/com/esri/core/geometry/OGCStructureInternal.java b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java new file mode 100644 index 00000000..dcf4aeef --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java @@ -0,0 +1,89 @@ +/* + Copyright 1995-2018 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import java.util.List; + +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCGeometryCollection; + +//An internal helper class. Do not use. +public class OGCStructureInternal { + private static class EditShapeCursor extends GeometryCursor { + EditShape m_shape; + int m_geom; + int m_index; + + EditShapeCursor(EditShape shape, int index) { + m_shape = shape; + m_geom = -1; + m_index = index; + } + @Override + public Geometry next() { + if (m_shape != null) { + if (m_geom == -1) + m_geom = m_shape.getFirstGeometry(); + else + m_geom = m_shape.getNextGeometry(m_geom); + + if (m_geom == -1) { + m_shape = null; + } + else { + return m_shape.getGeometry(m_geom); + } + + } + + return null; + } + + @Override + public int getGeometryID() { + return m_shape.getGeometryUserIndex(m_geom, m_index); + } + + }; + + public static GeometryCursor prepare_for_ops_(GeometryCursor geoms, SpatialReference sr) { + assert(geoms != null); + EditShape editShape = new EditShape(); + int geomIndex = editShape.createGeometryUserIndex(); + for (Geometry g = geoms.next(); g != null; g = geoms.next()) { + int egeom = editShape.addGeometry(g); + editShape.setGeometryUserIndex(egeom, geomIndex, geoms.getGeometryID()); + } + + Envelope2D env = editShape.getEnvelope2D(); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env, true); + + CrackAndCluster.execute(editShape, tolerance, null, true); + return OperatorSimplifyOGC.local().execute(new EditShapeCursor(editShape, geomIndex), sr, false, null); + } +} + From 559fe2f61c9c81ee741985d3a034f646bf8fedc8 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 29 May 2018 18:38:33 -0700 Subject: [PATCH 063/116] Fixed a couple of review comments --- .../ogc/OGCConcreteGeometryCollection.java | 12 ++++++++++-- .../com/esri/core/geometry/ogc/OGCGeometry.java | 8 ++++++++ src/test/java/com/esri/core/geometry/TestOGC.java | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 2e931696..35807cd2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -522,10 +522,10 @@ public int hashCode() { @Override public double distance(OGCGeometry another) { - double minD = 0; + double minD = Double.NaN; for (int i = 0, n = numGeometries(); i < n; ++i) { double d = geometryN(i).distance(another); - if (d < minD) { + if (d < minD || Double.isNaN(minD)) { minD = d; if (minD == 0) { break; @@ -612,6 +612,7 @@ public boolean isFlattened() { if (n > 3) return false; + int dimension = -1; for (int i = 0; i < n; ++i) { OGCGeometry g = geometryN(i); if (g.isEmpty()) @@ -620,6 +621,13 @@ public boolean isFlattened() { String t = g.geometryType(); if (t != OGCMultiPoint.TYPE && t != OGCMultiPolygon.TYPE && t != OGCMultiLineString.TYPE) return false; + + //check strict order of geometry dimensions + int d = g.dimension(); + if (d <= dimension) + return false; + + dimension = d; } return true; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 158d79ef..72093351 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -270,6 +270,10 @@ public boolean equals(OGCGeometry another) { } public boolean disjoint(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.disjoint(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.disjoint(geom1, geom2, @@ -299,6 +303,10 @@ public boolean within(OGCGeometry another) { } public boolean contains(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.contains(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.contains(geom1, geom2, diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index c0a744cc..a8afb7da 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1004,11 +1004,25 @@ public void testContainsOnGeometryCollection() { public void testIntersectsOnGeometryCollection() { OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); assertTrue(ogcGeometry.intersects(OGCGeometry.fromText("POINT (1 1)"))); + ogcGeometry = OGCGeometry.fromText("POINT (1 1)"); + assertTrue(ogcGeometry.intersects(OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"))); } @Test public void testDistanceOnGeometryCollection() { OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); assertTrue(ogcGeometry.distance(OGCGeometry.fromText("POINT (1 1)")) == 0); + + //distance to empty is NAN + ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(Double.isNaN(ogcGeometry.distance(OGCGeometry.fromText("POINT EMPTY")))); + } + + @Test + public void testFlattened() { + OGCConcreteGeometryCollection ogcGeometry = (OGCConcreteGeometryCollection)OGCGeometry.fromText("GEOMETRYCOLLECTION (MULTILINESTRING ((1 2, 3 4)), MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2))), MULTIPOINT (1 1))"); + assertFalse(ogcGeometry.isFlattened()); + ogcGeometry = (OGCConcreteGeometryCollection)OGCGeometry.fromText("GEOMETRYCOLLECTION (MULTIPOINT (1 1), MULTILINESTRING ((1 2, 3 4)), MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2))))"); + assertTrue(ogcGeometry.isFlattened()); } } From 0618e303c9e3958c3dac68eb38706d6b664a2f9a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 29 May 2018 19:36:58 -0700 Subject: [PATCH 064/116] a few fixes, added intersection and difference for collections --- .../com/esri/core/geometry/OGCStructure.java | 5 - .../core/geometry/OGCStructureInternal.java | 7 -- .../ogc/OGCConcreteGeometryCollection.java | 101 ++++++++++++++++-- .../esri/core/geometry/ogc/OGCGeometry.java | 13 ++- .../java/com/esri/core/geometry/TestOGC.java | 12 +++ 5 files changed, 119 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index 1a9891d9..9f0875b9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -23,13 +23,8 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; import java.util.List; -import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCGeometryCollection; - public class OGCStructure { public int m_type; public List m_structures; diff --git a/src/main/java/com/esri/core/geometry/OGCStructureInternal.java b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java index dcf4aeef..59cf1e61 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructureInternal.java +++ b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java @@ -23,13 +23,6 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; -import java.util.List; - -import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCGeometryCollection; - //An internal helper class. Do not use. public class OGCStructureInternal { private static class EditShapeCursor extends GeometryCursor { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 35807cd2..c7737ab0 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -80,6 +80,11 @@ public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { esriSR = sr; } + public OGCConcreteGeometryCollection(SpatialReference sr) { + geometries = new ArrayList(); + esriSR = sr; + } + @Override public int dimension() { int maxD = 0; @@ -356,7 +361,7 @@ protected boolean isConcreteGeometryCollection() { return true; } - static class GeometryCursorOGC extends GeometryCursor { + private static class GeometryCursorOGC extends GeometryCursor { private int m_index; private int m_ind; private List m_geoms; @@ -522,6 +527,9 @@ public int hashCode() { @Override public double distance(OGCGeometry another) { + if (this == another) + return isEmpty() ? Double.NaN : 0; + double minD = Double.NaN; for (int i = 0, n = numGeometries(); i < n; ++i) { double d = geometryN(i).distance(another); @@ -535,6 +543,8 @@ public double distance(OGCGeometry another) { return minD; } + + // //Relational operations @Override public boolean disjoint(OGCGeometry another) { @@ -569,7 +579,8 @@ public boolean disjoint(OGCGeometry another) { @Override public boolean contains(OGCGeometry another) { if (isEmpty() || another.isEmpty()) - return true; + return false; + if (this == another) return false; @@ -601,7 +612,79 @@ public boolean contains(OGCGeometry another) { //each geometry of another is contained in a geometry from this. return true; } - + //Topological + @Override + public OGCGeometry difference(OGCGeometry another) { + List list = wrapGeomsIntoList_(this, another); + list = prepare_for_ops_(list); + if (list.size() != 2) // this should not happen + throw new GeometryException("internal error"); + + List result = new ArrayList(); + OGCConcreteGeometryCollection coll1 = list.get(0); + OGCConcreteGeometryCollection coll2 = list.get(1); + for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { + OGCGeometry cur = coll1.geometryN(i); + for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { + OGCGeometry geom2 = coll2.geometryN(j); + if (cur.dimension() > geom2.dimension()) + continue; //subtracting lower dimension has no effect. + + cur = cur.difference(geom2); + if (cur.isEmpty()) + break; + } + + if (cur.isEmpty()) + continue; + + result.add(cur); + } + + return new OGCConcreteGeometryCollection(result, esriSR); + } + + @Override + public OGCGeometry intersection(OGCGeometry another) { + List list = wrapGeomsIntoList_(this, another); + list = prepare_for_ops_(list); + if (list.size() != 2) // this should not happen + throw new GeometryException("internal error"); + + List result = new ArrayList(); + OGCConcreteGeometryCollection coll1 = list.get(0); + OGCConcreteGeometryCollection coll2 = list.get(1); + for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { + OGCGeometry cur = coll1.geometryN(i); + for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { + OGCGeometry geom2 = coll2.geometryN(j); + + OGCGeometry partialIntersection = cur.intersection(geom2); + if (partialIntersection.isEmpty()) + continue; + + result.add(partialIntersection); + } + } + + return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); + } + + //make a list of collections out of two geometries + private static List wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) { + List list = new ArrayList(); + if (g1.geometryType() != OGCConcreteGeometryCollection.TYPE) { + g1 = new OGCConcreteGeometryCollection(g1, g1.getEsriSpatialReference()); + } + if (g2.geometryType() != OGCConcreteGeometryCollection.TYPE) { + g2 = new OGCConcreteGeometryCollection(g2, g2.getEsriSpatialReference()); + } + + list.add((OGCConcreteGeometryCollection)g1); + list.add((OGCConcreteGeometryCollection)g2); + + return list; + } /** * Checks if collection is flattened. * @return True for the flattened collection. A flattened collection contains up to three non-empty geometries: @@ -636,7 +719,7 @@ public boolean isFlattened() { /** * Flattens Geometry Collection. * The result collection contains up to three geometries: - * an OGCMultiPoint, an OGCMultiPolygon, and an OGCMultilineString. + * an OGCMultiPoint, an OGCMultilineString, and an OGCMultiPolygon (in that order). * @return A flattened Geometry Collection, or self if already flattened. */ public OGCConcreteGeometryCollection flatten() { @@ -804,17 +887,23 @@ private List prepare_for_ops_(List result = new ArrayList(); + List result = new ArrayList(); int prevCollectionIndex = -1; - ArrayList list = null; + List list = null; for (Geometry g = prepared.next(); g != null; g = prepared.next()) { int c = prepared.getGeometryID(); if (c != prevCollectionIndex) { + //add empty collections for all skipped indices + for (int i = prevCollectionIndex; i < c - 1; i++) { + result.add(new OGCConcreteGeometryCollection(esriSR)); + } + if (list != null) { result.add(removeOverlapsHelper_(list)); } list = new ArrayList(); + prevCollectionIndex = c; } list.add(g); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 72093351..8fda3edb 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -253,7 +253,7 @@ public boolean isMeasured() { */ public boolean Equals(OGCGeometry another) { if (this == another) - return true; + return true; //should return false for empty if (another == null) return false; @@ -333,6 +333,9 @@ public boolean relate(OGCGeometry another, String matrix) { // analysis public double distance(OGCGeometry another) { + if (this == another) + return isEmpty() ? Double.NaN : 0; + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { return another.distance(this); } @@ -456,6 +459,10 @@ public OGCGeometry convexHull() { } public OGCGeometry intersection(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return (new OGCConcreteGeometryCollection(this, esriSR)).intersection(another); + } + com.esri.core.geometry.OperatorIntersection op = (OperatorIntersection) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Intersection); com.esri.core.geometry.GeometryCursor cursor = op.execute( @@ -487,6 +494,10 @@ public OGCGeometry union(OGCGeometry another) { } public OGCGeometry difference(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return (new OGCConcreteGeometryCollection(this, esriSR)).difference(another); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return createFromEsriGeometry( diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index a8afb7da..b4dadf38 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -983,6 +983,18 @@ public void testUnionGeometryCollectionWithGeometryCollection() { "GEOMETRYCOLLECTION (POINT (3 5), LINESTRING (1 2, 2 3, 3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)))"); } + @Test + public void testIntersectionGeometryCollectionWithGeometryCollection() { + assertIntersection("GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))", + "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3), POINT (0.5 0.5), POINT (3 5), LINESTRING (3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 0)))", + "GEOMETRYCOLLECTION (MULTIPOINT ((1 2), (2 3), (3 4)), LINESTRING (0 0, 0.5 0.5, 1 1))"); + } + + private void assertIntersection(String leftWkt, String rightWkt, String expectedWkt) { + OGCGeometry intersection = OGCGeometry.fromText(leftWkt).intersection(OGCGeometry.fromText(rightWkt)); + assertEquals(expectedWkt, intersection.asText()); + } + private void assertUnion(String leftWkt, String rightWkt, String expectedWkt) { OGCGeometry union = OGCGeometry.fromText(leftWkt).union(OGCGeometry.fromText(rightWkt)); assertEquals(expectedWkt, union.asText()); From af34eaf7e156346a588dca57b9118f6e790079c3 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 30 May 2018 21:40:45 -0700 Subject: [PATCH 065/116] Equals for collection, reducFromMulti --- .../ogc/OGCConcreteGeometryCollection.java | 70 ++++++++++++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 21 +++++- .../esri/core/geometry/ogc/OGCLineString.java | 7 +- .../core/geometry/ogc/OGCMultiLineString.java | 16 ++++- .../esri/core/geometry/ogc/OGCMultiPoint.java | 14 ++++ .../core/geometry/ogc/OGCMultiPolygon.java | 16 ++++- .../com/esri/core/geometry/ogc/OGCPoint.java | 7 +- .../esri/core/geometry/ogc/OGCPolygon.java | 7 +- .../java/com/esri/core/geometry/TestOGC.java | 4 +- 9 files changed, 149 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index c7737ab0..74e5876b 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -477,6 +477,20 @@ public void setSpatialReference(SpatialReference esriSR_) { public OGCGeometry convertToMulti() { return this; } + + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return this; + } + + if (n == 1) { + return geometryN(0).reduceFromMulti(); + } + + return this; + } @Override public String asJson() { @@ -582,7 +596,7 @@ public boolean contains(OGCGeometry another) { return false; if (this == another) - return false; + return true; //TODO: a simple envelope test @@ -612,6 +626,48 @@ public boolean contains(OGCGeometry another) { //each geometry of another is contained in a geometry from this. return true; } + + @Override + public boolean Equals(OGCGeometry another) { + if (this == another) + return !isEmpty(); + + if (another == null) + return false; + + + OGCGeometry g1 = reduceFromMulti(); + String t1 = g1.geometryType(); + OGCGeometry g2 = reduceFromMulti(); + if (t1 != g2.geometryType()) { + return false; + } + + if (t1 != OGCConcreteGeometryCollection.TYPE) { + return g1.Equals(g2); + } + + OGCConcreteGeometryCollection gc1 = (OGCConcreteGeometryCollection)g1; + OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection)g2; + gc1 = gc1.flattenAndRemoveOverlaps(); + gc2 = gc2.flattenAndRemoveOverlaps(); + int n = gc1.numGeometries(); + if (n != gc2.numGeometries()) { + return false; + } + + boolean res = false; + for (int i = 0; i < n; ++i) { + if (!gc1.geometryN(i).Equals(gc2.geometryN(i))) { + return false; + } + + res = true; + } + + return res; + } + //Topological @Override public OGCGeometry difference(OGCGeometry another) { @@ -641,9 +697,13 @@ public OGCGeometry difference(OGCGeometry another) { result.add(cur); } + if (result.size() == 1) { + result.get(0).reduceFromMulti(); + } + return new OGCConcreteGeometryCollection(result, esriSR); } - + @Override public OGCGeometry intersection(OGCGeometry another) { List list = wrapGeomsIntoList_(this, another); @@ -666,6 +726,10 @@ public OGCGeometry intersection(OGCGeometry another) { result.add(partialIntersection); } } + + if (result.size() == 1) { + result.get(0).reduceFromMulti(); + } return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 8fda3edb..5014e207 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -253,17 +253,21 @@ public boolean isMeasured() { */ public boolean Equals(OGCGeometry another) { if (this == another) - return true; //should return false for empty + return !isEmpty(); if (another == null) return false; + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.Equals(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, getEsriSpatialReference()); } - + @Deprecated public boolean equals(OGCGeometry another) { return Equals(another); @@ -481,7 +485,7 @@ public OGCGeometry union(OGCGeometry another) { geoms.add(this); geoms.add(another); OGCConcreteGeometryCollection geomCol = new OGCConcreteGeometryCollection(geoms, esriSR); - return geomCol.flattenAndRemoveOverlaps(); + return geomCol.flattenAndRemoveOverlaps().reduceFromMulti(); } OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() @@ -755,6 +759,17 @@ public void setSpatialReference(SpatialReference esriSR_) { */ public abstract OGCGeometry convertToMulti(); + /** + * For the geometry collection types, when it has 1 or 0 elements, converts a MultiPolygon to Polygon, + * MultiPoint to Point, MultiLineString to a LineString, and + * OGCConcretGeometryCollection to the reduced element it contains. + * + * If OGCConcretGeometryCollection is empty, returns self. + * + * @return A reduced geometry or this. + */ + public abstract OGCGeometry reduceFromMulti(); + @Override public String toString() { String snippet = asText(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 4df18c15..f044128d 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -151,5 +151,10 @@ public OGCGeometry convertToMulti() return new OGCMultiLineString((Polyline)multiPath, esriSR); } + @Override + public OGCGeometry reduceFromMulti() { + return this; + } + MultiPath multiPath; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 91a6580e..6a2381e3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -123,5 +123,19 @@ public OGCGeometry convertToMulti() return this; } + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return new OGCLineString(new Polyline(polyline.getDescription()), 0, esriSR); + } + + if (n == 1) { + return geometryN(0); + } + + return this; + } + Polyline polyline; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 4f300ea3..b1c70a02 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -133,5 +133,19 @@ public OGCGeometry convertToMulti() return this; } + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return new OGCPoint(new Point(multiPoint.getDescription()), esriSR); + } + + if (n == 1) { + return geometryN(0); + } + + return this; + } + private com.esri.core.geometry.MultiPoint multiPoint; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 520cc3f7..d2e64956 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -135,5 +135,19 @@ public OGCGeometry convertToMulti() return this; } + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return new OGCLineString(new Polygon(polygon.getDescription()), 0, esriSR); + } + + if (n == 1) { + return geometryN(0); + } + + return this; + } + Polygon polygon; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 608e809f..d0fba6e7 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -115,6 +115,11 @@ public OGCGeometry convertToMulti() { return new OGCMultiPoint(point, esriSR); } + + @Override + public OGCGeometry reduceFromMulti() { + return this; + } com.esri.core.geometry.Point point; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 2bc65935..4c95250a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -143,5 +143,10 @@ public OGCGeometry convertToMulti() return new OGCMultiPolygon(polygon, esriSR); } + @Override + public OGCGeometry reduceFromMulti() { + return this; + } + Polygon polygon; } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index b4dadf38..eb40a2d4 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -957,7 +957,7 @@ public void testEmptyBoundary() throws Exception { @Test public void testUnionPointWithEmptyLineString() { - assertUnion("POINT (1 2)", "LINESTRING EMPTY", "GEOMETRYCOLLECTION (POINT (1 2))"); + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); } @Test @@ -967,7 +967,7 @@ public void testUnionPointWithLinestring() { @Test public void testUnionLinestringWithEmptyPolygon() { - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))"); + assertUnion("MULTILINESTRING ((1 2, 3 4))", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); } @Test From aefd7d73ed31149f9a3d1e795d0ec1c856fac690 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 3 Jun 2018 17:28:56 -0700 Subject: [PATCH 066/116] update some comments --- .../geometry/OperatorDifferenceLocal.java | 4 +- .../esri/core/geometry/TestDifference.java | 12 +- .../java/com/esri/core/geometry/TestOGC.java | 3 +- .../geometry/TestOGCGeometryCollection.java | 153 ++++++++++++++++++ 4 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index 02006bfc..b68b050f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -81,9 +81,9 @@ static Geometry difference(Geometry geometry_a, Geometry geometry_b, if (!env_a_inflated.isIntersecting(env_b)) return geometry_a; - if (dimension_a == 1 && dimension_b == 2) + /*if (dimension_a == 1 && dimension_b == 2) return polylineMinusArea_(geometry_a, geometry_b, type_b, - spatial_reference, progress_tracker); + spatial_reference, progress_tracker);*/ if (type_a == Geometry.GeometryType.Point) { Geometry geometry_b_; diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index c6f22321..2ea73ffc 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,8 +24,6 @@ package com.esri.core.geometry; -import java.util.ArrayList; -import java.util.List; import junit.framework.TestCase; @@ -504,6 +502,14 @@ public static void testDifferenceOnPolyline() { assertEquals(5, pointCountDiffPolyline); } + + @Test + public static void testDifferencePolylineAlongPolygonBoundary() { + Polyline polyline = (Polyline)GeometryEngine.geometryFromWkt("LINESTRING(0 0, 0 5, -2 5)", 0, Geometry.Type.Unknown); + Polygon polygon = (Polygon)GeometryEngine.geometryFromWkt("POLYGON((0 0, 5 0, 5 5, 0 5, 0 0))", 0, Geometry.Type.Unknown); + Geometry result = OperatorDifference.local().execute(polyline, polygon, null, null); + assertEquals(GeometryEngine.geometryToJson(null, result), "{\"paths\":[[[0,5],[-2,5]]]}"); + } public static Polygon makePolygon1() { Polygon poly = new Polygon(); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index eb40a2d4..34403661 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; import com.esri.core.geometry.ogc.OGCPolygon; -import com.fasterxml.jackson.core.JsonParseException; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import org.junit.Test; diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java new file mode 100644 index 00000000..1d1a32d7 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -0,0 +1,153 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Assert; +import org.junit.Test; + +public class TestOGCGeometryCollection +{ + @Test + public void testUnionPoint() + { + // point - point + assertUnion("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + + // point - multi-point + assertUnion("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4, 5 6)", "MULTIPOINT ((1 2), (3 4), (5 6))"); + + // point - linestring + assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "LINESTRING (1 1, 1 3, 3 4)", "LINESTRING (1 1, 1 2, 1 3, 3 4)"); + + // point - multi-linestring + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6))", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "MULTILINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12))", "GEOMETRYCOLLECTION (POINT (1 2), MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12)))"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 1, 1 3, 3 4), (7 8, 9 10, 11 12))", "MULTILINESTRING ((1 1, 1 2, 1 3, 3 4), (7 8, 9 10, 11 12))"); + + // point - polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "POLYGON EMPTY", "POINT (1 2)"); + // point inside polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + // point inside polygon with a hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))"); + // point inside polygon's hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + // point is a vertex of the polygon + assertUnion("POINT (1 2)", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))"); + // point on the boundary of the polybon + assertUnion("POINT (1 2)", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 1))", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 2, 1 1))"); + + // point - multi-polygon + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4)))", "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4))))"); + + // point - geometry collection + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2, 2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))"); + } + + @Test + public void testUnionLinestring() + { + // linestring - linestring + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 1 2)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 5 6)", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (5 6, 7 8)", "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 1, 2 5)", "MULTILINESTRING ((2 1, 2 3), (1 2, 2 3), (2 3, 3 4), (2 3, 2 5))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 2 3, 4 3)", "MULTILINESTRING ((1 2, 2 3), (2 3, 4 3), (2 3, 3 4))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 3, 2.1 3.1)", "LINESTRING (1 2, 2 3, 2.0999999999999996 3.0999999999999996, 3 4)"); + + // linestring - polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((5 5, 6 5, 6 6, 5 5))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((5 5, 6 5, 6 6, 5 5)))"); + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); + // linestring inside polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + assertUnion("LINESTRING (0 0, 5 0)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // linestring crosses polygon's vertex + assertUnion("LINESTRING (0 0, 6 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (5 5, 6 6), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + assertUnion("LINESTRING (4 6, 6 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (4 6, 5 5, 6 4), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + // linestring crosses polygon's boundary + assertUnion("LINESTRING (1 1, 1 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (1 5, 1 6), POLYGON ((0 0, 5 0, 5 5, 1 5, 0 5, 0 0)))"); + + // linestring - geometry collection + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6), LINESTRING (7 8, 9 10))", "MULTILINESTRING ((1 2, 3 4, 5 6), (7 8, 9 10))"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))"); + } + + @Test + public void testUnionPolygon() + { + // polygon - polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + // one polygon contains the other + assertUnion("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((1 1, 2 1, 2 2, 1 1))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // The following test fails because vertex order in the union geometry depends on the order of union inputs + //assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0))", "POLYGON ((0 0, 0.5 0, 1 0, 1 1, 0.49999999999999994 0.49999999999999994, 0 0))"); + // polygons intersect + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + // disjoint polygons + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((3 3, 5 3, 5 5, 3 3))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - multi-polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5)))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - geometry collection + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (0 0), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (10 10), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "GEOMETRYCOLLECTION (POINT (10 10), MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3))))"); + } + + private void assertUnion(String wkt, String otherWkt, String expectedWkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertEquals(expectedWkt, geometry.union(otherGeometry).asText()); + Assert.assertEquals(expectedWkt, otherGeometry.union(geometry).asText()); + } +} From b06e5793b00a4369cae7c559606147a29d910683 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 3 Jun 2018 17:32:24 -0700 Subject: [PATCH 067/116] comment out unused code --- .../com/esri/core/geometry/OperatorDifferenceLocal.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index b68b050f..e52e670d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -358,7 +358,9 @@ static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, return new_multipoint; } - static Geometry polylineMinusArea_(Geometry geometry, Geometry area, + /* + This optimization causes a bug when polyline segments are on the boundary. + static Geometry polylineMinusArea_(Geometry geometry, Geometry area, int area_type, SpatialReference sr, ProgressTracker progress_tracker) { // construct the complement of the Polygon (or Envelope) Envelope envelope = new Envelope(); @@ -387,6 +389,6 @@ static Geometry polylineMinusArea_(Geometry geometry, Geometry area, Geometry difference = operatorIntersection.execute(geometry, complement, sr, progress_tracker); return difference; - } + }*/ } From c47a0e49ba6b258cf0025cb6c9d9d2675cb0c4c6 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 4 Jun 2018 20:36:39 -0700 Subject: [PATCH 068/116] reflect review comments. fix formatting in a test --- .../geometry/OperatorDifferenceLocal.java | 39 +-- .../ogc/OGCConcreteGeometryCollection.java | 28 +- .../geometry/TestOGCGeometryCollection.java | 319 ++++++++++-------- 3 files changed, 188 insertions(+), 198 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index e52e670d..31db5027 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -81,10 +81,6 @@ static Geometry difference(Geometry geometry_a, Geometry geometry_b, if (!env_a_inflated.isIntersecting(env_b)) return geometry_a; - /*if (dimension_a == 1 && dimension_b == 2) - return polylineMinusArea_(geometry_a, geometry_b, type_b, - spatial_reference, progress_tracker);*/ - if (type_a == Geometry.GeometryType.Point) { Geometry geometry_b_; if (MultiPath.isSegment(type_b)) { @@ -357,38 +353,5 @@ static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, return new_multipoint; } - - /* - This optimization causes a bug when polyline segments are on the boundary. - static Geometry polylineMinusArea_(Geometry geometry, Geometry area, - int area_type, SpatialReference sr, ProgressTracker progress_tracker) { - // construct the complement of the Polygon (or Envelope) - Envelope envelope = new Envelope(); - geometry.queryEnvelope(envelope); - Envelope2D env_2D = new Envelope2D(); - area.queryEnvelope2D(env_2D); - envelope.merge(env_2D); - double dw = 0.1 * envelope.getWidth(); - double dh = 0.1 * envelope.getHeight(); - envelope.inflate(dw, dh); - - Polygon complement = new Polygon(); - complement.addEnvelope(envelope, false); - - MultiPathImpl complementImpl = (MultiPathImpl) (complement._getImpl()); - - if (area_type == Geometry.GeometryType.Polygon) { - MultiPathImpl polygonImpl = (MultiPathImpl) (area._getImpl()); - complementImpl.add(polygonImpl, true); - } else - complementImpl.addEnvelope((Envelope) (area), true); - - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry difference = operatorIntersection.execute(geometry, - complement, sr, progress_tracker); - return difference; - }*/ - } + diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 74e5876b..d0a82e3f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -598,33 +598,7 @@ public boolean contains(OGCGeometry another) { if (this == another) return true; - //TODO: a simple envelope test - - OGCConcreteGeometryCollection flattened1 = flatten(); - if (flattened1.isEmpty()) - return true; - OGCConcreteGeometryCollection otherCol = new OGCConcreteGeometryCollection(another, esriSR); - OGCConcreteGeometryCollection flattened2 = otherCol.flatten(); - if (flattened2.isEmpty()) - return true; - - for (int i = 0, n2 = flattened2.numGeometries(); i < n2; ++i) { - OGCGeometry g2 = flattened2.geometryN(i); - boolean good = false; - for (int j = 0, n1 = flattened1.numGeometries(); j < n1; ++j) { - OGCGeometry g1 = flattened1.geometryN(i); - if (g1.contains(g2)) { - good = true; - break; - } - } - - if (!good) - return false; - } - - //each geometry of another is contained in a geometry from this. - return true; + return another.difference(this).isEmpty(); } @Override diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java index 1d1a32d7..deadad18 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -17,137 +17,190 @@ import org.junit.Assert; import org.junit.Test; -public class TestOGCGeometryCollection -{ - @Test - public void testUnionPoint() - { - // point - point - assertUnion("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); - assertUnion("POINT (1 2)", "POINT EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "POINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); - - // point - multi-point - assertUnion("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTIPOINT EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTIPOINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); - assertUnion("POINT (1 2)", "MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); - assertUnion("POINT (1 2)", "MULTIPOINT (3 4, 5 6)", "MULTIPOINT ((1 2), (3 4), (5 6))"); - - // point - linestring - assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); - assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); - assertUnion("POINT (1 2)", "LINESTRING (1 1, 1 3, 3 4)", "LINESTRING (1 1, 1 2, 1 3, 3 4)"); - - // point - multi-linestring - assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6))", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); - assertUnion("POINT (1 2)", "MULTILINESTRING EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12))", "GEOMETRYCOLLECTION (POINT (1 2), MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12)))"); - assertUnion("POINT (1 2)", "MULTILINESTRING ((1 2, 3 4))", "LINESTRING (1 2, 3 4)"); - assertUnion("POINT (1 2)", "MULTILINESTRING ((1 1, 1 3, 3 4), (7 8, 9 10, 11 12))", "MULTILINESTRING ((1 1, 1 2, 1 3, 3 4), (7 8, 9 10, 11 12))"); - - // point - polygon - assertUnion("POINT (1 2)", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); - assertUnion("POINT (1 2)", "POLYGON EMPTY", "POINT (1 2)"); - // point inside polygon - assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); - // point inside polygon with a hole - assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))"); - // point inside polygon's hole - assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); - // point is a vertex of the polygon - assertUnion("POINT (1 2)", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))"); - // point on the boundary of the polybon - assertUnion("POINT (1 2)", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 1))", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 2, 1 1))"); - - // point - multi-polygon - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4)))", "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4))))"); - - // point - geometry collection - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3))", "MULTIPOINT ((1 2), (2 3))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2, 2 3))", "MULTIPOINT ((1 2), (2 3))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))"); - } - - @Test - public void testUnionLinestring() - { - // linestring - linestring - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING EMPTY", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 1 2)", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 5 6)", "LINESTRING (1 2, 3 4, 5 6)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (5 6, 7 8)", "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 1, 2 5)", "MULTILINESTRING ((2 1, 2 3), (1 2, 2 3), (2 3, 3 4), (2 3, 2 5))"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 2 3, 4 3)", "MULTILINESTRING ((1 2, 2 3), (2 3, 4 3), (2 3, 3 4))"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 3, 2.1 3.1)", "LINESTRING (1 2, 2 3, 2.0999999999999996 3.0999999999999996, 3 4)"); - - // linestring - polygon - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((5 5, 6 5, 6 6, 5 5))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((5 5, 6 5, 6 6, 5 5)))"); - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); - // linestring inside polygon - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); - assertUnion("LINESTRING (0 0, 5 0)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); - // linestring crosses polygon's vertex - assertUnion("LINESTRING (0 0, 6 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (5 5, 6 6), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); - assertUnion("LINESTRING (4 6, 6 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (4 6, 5 5, 6 4), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); - // linestring crosses polygon's boundary - assertUnion("LINESTRING (1 1, 1 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (1 5, 1 6), POLYGON ((0 0, 5 0, 5 5, 1 5, 0 5, 0 0)))"); - - // linestring - geometry collection - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION EMPTY", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6), LINESTRING (7 8, 9 10))", "MULTILINESTRING ((1 2, 3 4, 5 6), (7 8, 9 10))"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))"); - } - - @Test - public void testUnionPolygon() - { - // polygon - polygon - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - // one polygon contains the other - assertUnion("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((1 1, 2 1, 2 2, 1 1))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); - // The following test fails because vertex order in the union geometry depends on the order of union inputs - //assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0))", "POLYGON ((0 0, 0.5 0, 1 0, 1 1, 0.49999999999999994 0.49999999999999994, 0 0))"); - // polygons intersect - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); - // disjoint polygons - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((3 3, 5 3, 5 5, 3 3))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - - // polygon - multi-polygon - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5)))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - - // polygon - geometry collection - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (0 0), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (10 10), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "GEOMETRYCOLLECTION (POINT (10 10), MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3))))"); - } - - private void assertUnion(String wkt, String otherWkt, String expectedWkt) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); - Assert.assertEquals(expectedWkt, geometry.union(otherGeometry).asText()); - Assert.assertEquals(expectedWkt, otherGeometry.union(geometry).asText()); - } +public class TestOGCGeometryCollection { + @Test + public void testUnionPoint() { + // point - point + assertUnion("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + + // point - multi-point + assertUnion("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4, 5 6)", "MULTIPOINT ((1 2), (3 4), (5 6))"); + + // point - linestring + assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "LINESTRING (1 1, 1 3, 3 4)", "LINESTRING (1 1, 1 2, 1 3, 3 4)"); + + // point - multi-linestring + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6))", + "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "MULTILINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12))", + "GEOMETRYCOLLECTION (POINT (1 2), MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12)))"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 1, 1 3, 3 4), (7 8, 9 10, 11 12))", + "MULTILINESTRING ((1 1, 1 2, 1 3, 3 4), (7 8, 9 10, 11 12))"); + + // point - polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 1 0, 1 1, 0 0))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "POLYGON EMPTY", "POINT (1 2)"); + // point inside polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + // point inside polygon with a hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))", + "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))"); + // point inside polygon's hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + // point is a vertex of the polygon + assertUnion("POINT (1 2)", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))"); + // point on the boundary of the polybon + assertUnion("POINT (1 2)", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 1))", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 2, 1 1))"); + + // point - multi-polygon + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))", + "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))"); + assertUnion("POINT (1 2)", + "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + assertUnion("POINT (1 2)", + "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4)))", + "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4))))"); + + // point - geometry collection + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2, 2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))", + "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", + "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", + "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))"); + } + + @Test + public void testUnionLinestring() { + // linestring - linestring + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 1 2)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 5 6)", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (5 6, 7 8)", "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 1, 2 5)", + "MULTILINESTRING ((2 1, 2 3), (1 2, 2 3), (2 3, 3 4), (2 3, 2 5))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 2 3, 4 3)", + "MULTILINESTRING ((1 2, 2 3), (2 3, 4 3), (2 3, 3 4))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 3, 2.1 3.1)", + "LINESTRING (1 2, 2 3, 2.0999999999999996 3.0999999999999996, 3 4)"); + + // linestring - polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((5 5, 6 5, 6 6, 5 5))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((5 5, 6 5, 6 6, 5 5)))"); + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); + // linestring inside polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + assertUnion("LINESTRING (0 0, 5 0)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // linestring crosses polygon's vertex + assertUnion("LINESTRING (0 0, 6 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (5 5, 6 6), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + assertUnion("LINESTRING (4 6, 6 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (4 6, 5 5, 6 4), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + // linestring crosses polygon's boundary + assertUnion("LINESTRING (1 1, 1 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (1 5, 1 6), POLYGON ((0 0, 5 0, 5 5, 1 5, 0 5, 0 0)))"); + + // linestring - geometry collection + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6))", + "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6), LINESTRING (7 8, 9 10))", + "MULTILINESTRING ((1 2, 3 4, 5 6), (7 8, 9 10))"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))", + "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", + "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))"); + } + + @Test + public void testUnionPolygon() { + // polygon - polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", + "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + // one polygon contains the other + assertUnion("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((1 1, 2 1, 2 2, 1 1))", + "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // The following test fails because vertex order in the union geometry depends + // on the order of union inputs + // assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 0.5 0, 0.5 0.5, + // 0 0))", "POLYGON ((0 0, 0.5 0, 1 0, 1 1, 0.49999999999999994 + // 0.49999999999999994, 0 0))"); + // polygons intersect + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5))", + "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + // disjoint polygons + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((3 3, 5 3, 5 5, 3 3))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - multi-polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", + "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5)))", + "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((3 3, 5 3, 5 5, 3 3)))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - geometry collection + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0 0)))", + "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((3 3, 5 3, 5 5, 3 3)))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", + "GEOMETRYCOLLECTION (POINT (0 0), POLYGON ((3 3, 5 3, 5 5, 3 3)))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", + "GEOMETRYCOLLECTION (POINT (10 10), POLYGON ((3 3, 5 3, 5 5, 3 3)))", + "GEOMETRYCOLLECTION (POINT (10 10), MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3))))"); + } + + private void assertUnion(String wkt, String otherWkt, String expectedWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertEquals(expectedWkt, geometry.union(otherGeometry).asText()); + Assert.assertEquals(expectedWkt, otherGeometry.union(geometry).asText()); + } + + @Test + public void testGeometryCollectionOverlappingContains() { + assertContains("GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 1, 5 1))", + "GEOMETRYCOLLECTION (MULTIPOINT (0 0, 2 1))"); + } + + private void assertContains(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertTrue(geometry.contains(otherGeometry)); + Assert.assertTrue(otherGeometry.within(geometry)); + } } From 873d073cbc66efd4616a8db2f0717538c646ac3f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 6 Jun 2018 20:18:22 -0700 Subject: [PATCH 069/116] fix a typo in disjoint and a test --- .../ogc/OGCConcreteGeometryCollection.java | 2 +- .../core/geometry/TestOGCGeometryCollection.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index d0a82e3f..004c7a8f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -581,7 +581,7 @@ public boolean disjoint(OGCGeometry another) { for (int i = 0, n1 = flattened1.numGeometries(); i < n1; ++i) { OGCGeometry g1 = flattened1.geometryN(i); for (int j = 0, n2 = flattened2.numGeometries(); j < n2; ++j) { - OGCGeometry g2 = flattened2.geometryN(i); + OGCGeometry g2 = flattened2.geometryN(j); if (!g1.disjoint(g2)) return false; } diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java index deadad18..c74752ee 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -203,4 +203,18 @@ private void assertContains(String wkt, String otherWkt) { Assert.assertTrue(geometry.contains(otherGeometry)); Assert.assertTrue(otherGeometry.within(geometry)); } + + @Test + public void testGeometryCollectionDisjoint() { + assertDisjoint("GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 1, 5 1))", + "GEOMETRYCOLLECTION (MULTIPOINT (10 0, 21 1), LINESTRING (30 0, 31 1), POLYGON ((40 0, 41 1, 40 1, 40 0)))"); + } + + private void assertDisjoint(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertTrue(geometry.disjoint(otherGeometry)); + Assert.assertTrue(otherGeometry.disjoint(geometry)); + } + } From 7e19e6f09d07c749b13aa346a946aea1aff413e2 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 7 Jun 2018 08:16:36 -0700 Subject: [PATCH 070/116] Reflect review comments, throw from unsupported methods --- .../ogc/OGCConcreteGeometryCollection.java | 37 ++++++++++++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 27 +++++++++++++- .../geometry/TestOGCGeometryCollection.java | 17 +++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 004c7a8f..3868a469 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -560,6 +560,29 @@ public double distance(OGCGeometry another) { // //Relational operations + @Override + public boolean overlaps(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean touches(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean crosses(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean relate(OGCGeometry another, String matrix) { + throw new UnsupportedOperationException(); + } + @Override public boolean disjoint(OGCGeometry another) { if (isEmpty() || another.isEmpty()) @@ -672,7 +695,7 @@ public OGCGeometry difference(OGCGeometry another) { } if (result.size() == 1) { - result.get(0).reduceFromMulti(); + return result.get(0).reduceFromMulti(); } return new OGCConcreteGeometryCollection(result, esriSR); @@ -680,6 +703,10 @@ public OGCGeometry difference(OGCGeometry another) { @Override public OGCGeometry intersection(OGCGeometry another) { + if (isEmpty() || another.isEmpty()) { + return new OGCConcreteGeometryCollection(esriSR); + } + List list = wrapGeomsIntoList_(this, another); list = prepare_for_ops_(list); if (list.size() != 2) // this should not happen @@ -702,11 +729,17 @@ public OGCGeometry intersection(OGCGeometry another) { } if (result.size() == 1) { - result.get(0).reduceFromMulti(); + return result.get(0).reduceFromMulti(); } return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); } + + @Override + public OGCGeometry symDifference(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } //make a list of collections out of two geometries private static List wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 5014e207..87e8c620 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -289,6 +289,11 @@ public boolean intersects(OGCGeometry another) { } public boolean touches(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + //TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.touches(geom1, geom2, @@ -296,6 +301,11 @@ public boolean touches(OGCGeometry another) { } public boolean crosses(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + //TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.crosses(geom1, geom2, @@ -308,7 +318,7 @@ public boolean within(OGCGeometry another) { public boolean contains(OGCGeometry another) { if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { - return another.contains(this); + return new OGCConcreteGeometryCollection(this, esriSR).contains(another); } com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); @@ -318,6 +328,10 @@ public boolean contains(OGCGeometry another) { } public boolean overlaps(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.overlaps(this); //overlaps should be symmetric + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.overlaps(geom1, geom2, @@ -325,6 +339,11 @@ public boolean overlaps(OGCGeometry another) { } public boolean relate(OGCGeometry another, String matrix) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + //TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.relate(geom1, geom2, @@ -337,8 +356,9 @@ public boolean relate(OGCGeometry another, String matrix) { // analysis public double distance(OGCGeometry another) { - if (this == another) + if (this == another) { return isEmpty() ? Double.NaN : 0; + } if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { return another.distance(this); @@ -510,6 +530,9 @@ public OGCGeometry difference(OGCGeometry another) { } public OGCGeometry symDifference(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) + return another.symDifference(this); + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return createFromEsriGeometry( diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java index c74752ee..53007166 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -216,5 +216,22 @@ private void assertDisjoint(String wkt, String otherWkt) { Assert.assertTrue(geometry.disjoint(otherGeometry)); Assert.assertTrue(otherGeometry.disjoint(geometry)); } + + @Test + public void testGeometryCollectionIntersect() { + assertIntersection("GEOMETRYCOLLECTION (POINT (1 2))", "POINT EMPTY", "GEOMETRYCOLLECTION EMPTY"); + assertIntersection("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (3 4)", + "POINT (3 4)"); + assertIntersection("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (30 40)", + "GEOMETRYCOLLECTION EMPTY"); + assertIntersection("POINT (30 40)", "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", + "GEOMETRYCOLLECTION EMPTY"); + } + private void assertIntersection(String wkt, String otherWkt, String expectedWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + OGCGeometry result = geometry.intersection(otherGeometry); + Assert.assertEquals(expectedWkt, result.asText()); + } } From 104c2ef50e834f48d270133464945963137dfc58 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 7 Jun 2018 08:18:41 -0700 Subject: [PATCH 071/116] Added a test from @mbasmanova --- .../esri/core/geometry/TestOGCContains.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCContains.java diff --git a/src/test/java/com/esri/core/geometry/TestOGCContains.java b/src/test/java/com/esri/core/geometry/TestOGCContains.java new file mode 100644 index 00000000..fd2c5116 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCContains.java @@ -0,0 +1,74 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestOGCContains { + @Test + public void testPoint() { + // point + assertContains("POINT (1 2)", "POINT (1 2)"); + assertContains("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))"); + assertNotContains("POINT (1 2)", "POINT EMPTY"); + assertNotContains("POINT (1 2)", "POINT (3 4)"); + + // multi-point + assertContains("MULTIPOINT (1 2, 3 4)", "MULTIPOINT (1 2, 3 4)"); + assertContains("MULTIPOINT (1 2, 3 4)", "MULTIPOINT (1 2)"); + assertContains("MULTIPOINT (1 2, 3 4)", "POINT (3 4)"); + assertContains("MULTIPOINT (1 2, 3 4)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2), POINT (3 4))"); + assertContains("MULTIPOINT (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2))"); + assertNotContains("MULTIPOINT (1 2, 3 4)", "MULTIPOINT EMPTY"); + } + + @Test + public void testLineString() { + // TODO Add more tests + assertContains("LINESTRING (0 1, 5 1)", "POINT (2 1)"); + } + + @Test + public void testPolygon() { + // TODO Fill in + } + + @Test + public void testGeometryCollection() { + // TODO Add more tests + assertContains("GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 1, 5 1))", + "GEOMETRYCOLLECTION (MULTIPOINT (0 0, 2 1))"); + } + + private void assertContains(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + + assertTrue(geometry.contains(otherGeometry)); + assertTrue(otherGeometry.within(geometry)); + assertFalse(geometry.disjoint(otherGeometry)); + } + + private void assertNotContains(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + assertFalse(geometry.contains(otherGeometry)); + assertFalse(otherGeometry.within(geometry)); + } +} + From 7030d57367fa2587cfcdc6786c42ee49cf1d5a2d Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Thu, 7 Jun 2018 22:40:24 -0400 Subject: [PATCH 072/116] Fix OGCCollection#reduceFromMulti and add test --- .../core/geometry/ogc/OGCMultiPolygon.java | 2 +- .../core/geometry/TestOGCReduceFromMulti.java | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index d2e64956..52afbe86 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -139,7 +139,7 @@ public OGCGeometry convertToMulti() public OGCGeometry reduceFromMulti() { int n = numGeometries(); if (n == 0) { - return new OGCLineString(new Polygon(polygon.getDescription()), 0, esriSR); + return new OGCPolygon(new Polygon(polygon.getDescription()), 0, esriSR); } if (n == 1) { diff --git a/src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java b/src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java new file mode 100644 index 00000000..f47c817c --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java @@ -0,0 +1,80 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import org.junit.Test; + +import static com.esri.core.geometry.ogc.OGCGeometry.fromText; +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; + +public class TestOGCReduceFromMulti +{ + @Test + public void testPoint() + { + assertReduceFromMulti("POINT (1 2)", "POINT (1 2)"); + assertReduceFromMulti("POINT EMPTY", "POINT EMPTY"); + assertReduceFromMulti("MULTIPOINT (1 2)", "POINT (1 2)"); + assertReduceFromMulti("MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertReduceFromMulti("MULTIPOINT EMPTY", "POINT EMPTY"); + } + + @Test + public void testLineString() + { + assertReduceFromMulti("LINESTRING (0 0, 1 1, 2 3)", "LINESTRING (0 0, 1 1, 2 3)"); + assertReduceFromMulti("LINESTRING EMPTY", "LINESTRING EMPTY"); + assertReduceFromMulti("MULTILINESTRING ((0 0, 1 1, 2 3))", "LINESTRING (0 0, 1 1, 2 3)"); + assertReduceFromMulti("MULTILINESTRING ((0 0, 1 1, 2 3), (4 4, 5 5))", "MULTILINESTRING ((0 0, 1 1, 2 3), (4 4, 5 5))"); + assertReduceFromMulti("MULTILINESTRING EMPTY", "LINESTRING EMPTY"); + } + + @Test + public void testPolygon() + { + assertReduceFromMulti("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertReduceFromMulti("POLYGON EMPTY", "POLYGON EMPTY"); + assertReduceFromMulti("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertReduceFromMulti("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))"); + assertReduceFromMulti("MULTIPOLYGON EMPTY", "POLYGON EMPTY"); + } + + @Test + public void testGeometryCollection() + { + assertReduceFromMulti(gc("POINT (1 2)"), "POINT (1 2)"); + assertReduceFromMulti(gc("MULTIPOINT (1 2)"), "POINT (1 2)"); + assertReduceFromMulti(gc(gc("POINT (1 2)")), "POINT (1 2)"); + assertReduceFromMulti(gc("POINT EMPTY"), "POINT EMPTY"); + + assertReduceFromMulti(gc("LINESTRING (0 0, 1 1, 2 3)"), "LINESTRING (0 0, 1 1, 2 3)"); + assertReduceFromMulti(gc("POLYGON ((0 0, 1 0, 1 1, 0 0))"), "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + + assertReduceFromMulti(gc("POINT (1 2), LINESTRING (0 0, 1 1, 2 3)"), gc("POINT (1 2), LINESTRING (0 0, 1 1, 2 3)")); + + assertReduceFromMulti("GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"); + assertReduceFromMulti(gc("GEOMETRYCOLLECTION EMPTY"), "GEOMETRYCOLLECTION EMPTY"); + } + + private void assertReduceFromMulti(String wkt, String reducedWkt) + { + assertEquals(reducedWkt, fromText(wkt).reduceFromMulti().asText()); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } +} From 40b324e8d01eec1d98eeac872fcca992bcb24c1a Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Fri, 8 Jun 2018 00:30:02 -0400 Subject: [PATCH 073/116] Add tests for distance and flatten --- .../ogc/OGCConcreteGeometryCollection.java | 2 + .../esri/core/geometry/TestOGCDistance.java | 59 +++++++++++++++++++ .../TestOGCGeometryCollectionFlatten.java | 48 +++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCDistance.java create mode 100644 src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 3868a469..660f70ed 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -546,9 +546,11 @@ public double distance(OGCGeometry another) { double minD = Double.NaN; for (int i = 0, n = numGeometries(); i < n; ++i) { + // TODO Skip expensive distance computation if bounding boxes are further away than minD double d = geometryN(i).distance(another); if (d < minD || Double.isNaN(minD)) { minD = d; + // TODO Replace zero with tolerance defined by the spatial reference if (minD == 0) { break; } diff --git a/src/test/java/com/esri/core/geometry/TestOGCDistance.java b/src/test/java/com/esri/core/geometry/TestOGCDistance.java new file mode 100644 index 00000000..dea491f9 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCDistance.java @@ -0,0 +1,59 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import org.junit.Test; + +import static com.esri.core.geometry.ogc.OGCGeometry.fromText; +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; + +public class TestOGCDistance +{ + @Test + public void testPoint() + { + assertDistance("POINT (1 2)", "POINT (2 2)", 1); + assertDistance("POINT (1 2)", "POINT (1 2)", 0); + assertNanDistance("POINT (1 2)", "POINT EMPTY"); + + assertDistance(gc("POINT (1 2)"), "POINT (2 2)", 1); + assertDistance(gc("POINT (1 2)"), "POINT (1 2)", 0); + assertNanDistance(gc("POINT (1 2)"), "POINT EMPTY"); + assertDistance(gc("POINT (1 2)"), gc("POINT (2 2)"), 1); + + assertDistance("MULTIPOINT (1 0, 2 0, 3 0)", "POINT (2 1)", 1); + assertDistance(gc("MULTIPOINT (1 0, 2 0, 3 0)"), "POINT (2 1)", 1); + assertDistance(gc("POINT (1 0), POINT (2 0), POINT (3 0))"), "POINT (2 1)", 1); + + assertDistance(gc("POINT (1 0), POINT EMPTY"), "POINT (2 0)", 1); + } + + private void assertDistance(String wkt, String otherWkt, double distance) + { + assertEquals(distance, fromText(wkt).distance(fromText(otherWkt)), 0.000001); + assertEquals(distance, fromText(otherWkt).distance(fromText(wkt)), 0.000001); + } + + private void assertNanDistance(String wkt, String otherWkt) + { + assertEquals(Double.NaN, fromText(wkt).distance(fromText(otherWkt)), 0.000001); + assertEquals(Double.NaN, fromText(otherWkt).distance(fromText(wkt)), 0.000001); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java new file mode 100644 index 00000000..50832f2d --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java @@ -0,0 +1,48 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Test; + +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestOGCGeometryCollectionFlatten +{ + @Test + public void test() + { + assertFlatten("GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"); + assertFlatten(gc("POINT (1 2)"), gc("MULTIPOINT ((1 2))")); + assertFlatten(gc("POINT (1 2), POINT EMPTY"), gc("MULTIPOINT ((1 2))")); + assertFlatten(gc("POINT (1 2), MULTIPOINT (3 4, 5 6)"), gc("MULTIPOINT ((1 2), (3 4), (5 6))")); + assertFlatten(gc("POINT (1 2), POINT (3 4), " + gc("POINT (5 6)")), gc("MULTIPOINT ((1 2), (3 4), (5 6))")); + } + + private void assertFlatten(String wkt, String flattenedWkt) + { + OGCConcreteGeometryCollection collection = (OGCConcreteGeometryCollection) OGCGeometry.fromText(wkt); + assertEquals(flattenedWkt, collection.flatten().asText()); + assertTrue(collection.flatten().isFlattened()); + assertEquals(flattenedWkt, collection.flatten().flatten().asText()); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } +} From 8c20f1d25f206d07bff65c5664f5ab702e30293c Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Fri, 8 Jun 2018 00:30:26 -0400 Subject: [PATCH 074/116] Simplify intersection and difference --- .../ogc/OGCConcreteGeometryCollection.java | 183 +++++++----------- .../esri/core/geometry/TestOGCDisjoint.java | 126 ++++++++++++ .../TestOGCGeometryCollectionFlatten.java | 4 +- 3 files changed, 203 insertions(+), 110 deletions(-) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCDisjoint.java diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 660f70ed..155b8c41 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -25,6 +25,7 @@ package com.esri.core.geometry.ogc; import com.esri.core.geometry.Envelope; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryException; @@ -35,15 +36,15 @@ import com.esri.core.geometry.OGCStructureInternal; import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.OperatorDifference; +import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.OperatorIntersection; +import com.esri.core.geometry.OperatorUnion; +import com.esri.core.geometry.Point; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SimpleGeometryCursor; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.VertexDescription; -import com.esri.core.geometry.GeoJsonExportFlags; -import com.esri.core.geometry.OperatorExportToGeoJson; -import com.esri.core.geometry.OperatorUnion; -import com.esri.core.geometry.Point; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -648,6 +649,8 @@ public boolean Equals(OGCGeometry another) { OGCConcreteGeometryCollection gc1 = (OGCConcreteGeometryCollection)g1; OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection)g2; + // TODO Assuming input geometries are simple and valid, remove-overlaps would be a no-op. + // Hence, calling flatten() should be sufficient. gc1 = gc1.flattenAndRemoveOverlaps(); gc2 = gc2.flattenAndRemoveOverlaps(); int n = gc1.numGeometries(); @@ -655,52 +658,66 @@ public boolean Equals(OGCGeometry another) { return false; } - boolean res = false; for (int i = 0; i < n; ++i) { if (!gc1.geometryN(i).Equals(gc2.geometryN(i))) { return false; } - - res = true; } - return res; + return n > 0; } - + + private static OGCConcreteGeometryCollection toGeometryCollection(OGCGeometry geometry) + { + if (geometry.geometryType() != OGCConcreteGeometryCollection.TYPE) { + return new OGCConcreteGeometryCollection(geometry, geometry.getEsriSpatialReference()); + } + + return (OGCConcreteGeometryCollection) geometry; + } + + private static List toList(GeometryCursor cursor) + { + List geometries = new ArrayList(); + for (Geometry geometry = cursor.next(); geometry != null; geometry = cursor.next()) { + geometries.add(geometry); + } + return geometries; + } + //Topological @Override public OGCGeometry difference(OGCGeometry another) { - List list = wrapGeomsIntoList_(this, another); - list = prepare_for_ops_(list); - if (list.size() != 2) // this should not happen - throw new GeometryException("internal error"); - + if (isEmpty() || another.isEmpty()) { + return this; + } + + List geometries = toList(prepare_for_ops_(toGeometryCollection(this))); + List otherGeometries = toList(prepare_for_ops_(toGeometryCollection(another))); + List result = new ArrayList(); - OGCConcreteGeometryCollection coll1 = list.get(0); - OGCConcreteGeometryCollection coll2 = list.get(1); - for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { - OGCGeometry cur = coll1.geometryN(i); - for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { - OGCGeometry geom2 = coll2.geometryN(j); - if (cur.dimension() > geom2.dimension()) + for (Geometry geometry : geometries) { + for (Geometry otherGeometry : otherGeometries) { + if (geometry.getDimension() > otherGeometry.getDimension()) { continue; //subtracting lower dimension has no effect. - - cur = cur.difference(geom2); - if (cur.isEmpty()) + } + + geometry = OperatorDifference.local().execute(geometry, otherGeometry, esriSR, null); + if (geometry.isEmpty()) { break; + } + } + + if (!geometry.isEmpty()) { + result.add(OGCGeometry.createFromEsriGeometry(geometry, esriSR)); } - - if (cur.isEmpty()) - continue; - - result.add(cur); } - + if (result.size() == 1) { return result.get(0).reduceFromMulti(); } - return new OGCConcreteGeometryCollection(result, esriSR); + return new OGCConcreteGeometryCollection(result, esriSR).flattenAndRemoveOverlaps(); } @Override @@ -708,25 +725,18 @@ public OGCGeometry intersection(OGCGeometry another) { if (isEmpty() || another.isEmpty()) { return new OGCConcreteGeometryCollection(esriSR); } - - List list = wrapGeomsIntoList_(this, another); - list = prepare_for_ops_(list); - if (list.size() != 2) // this should not happen - throw new GeometryException("internal error"); - + + List geometries = toList(prepare_for_ops_(toGeometryCollection(this))); + List otherGeometries = toList(prepare_for_ops_(toGeometryCollection(another))); + List result = new ArrayList(); - OGCConcreteGeometryCollection coll1 = list.get(0); - OGCConcreteGeometryCollection coll2 = list.get(1); - for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { - OGCGeometry cur = coll1.geometryN(i); - for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { - OGCGeometry geom2 = coll2.geometryN(j); - - OGCGeometry partialIntersection = cur.intersection(geom2); - if (partialIntersection.isEmpty()) - continue; - - result.add(partialIntersection); + for (Geometry geometry : geometries) { + for (Geometry otherGeometry : otherGeometries) { + GeometryCursor intersectionCursor = OperatorIntersection.local().execute(new SimpleGeometryCursor(geometry), new SimpleGeometryCursor(otherGeometry), esriSR, null, 7); + OGCGeometry intersection = OGCGeometry.createFromEsriCursor(intersectionCursor, esriSR, true); + if (!intersection.isEmpty()) { + result.add(intersection); + } } } @@ -734,7 +744,7 @@ public OGCGeometry intersection(OGCGeometry another) { return result.get(0).reduceFromMulti(); } - return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); + return new OGCConcreteGeometryCollection(result, esriSR).flattenAndRemoveOverlaps(); } @Override @@ -743,21 +753,6 @@ public OGCGeometry symDifference(OGCGeometry another) { throw new UnsupportedOperationException(); } - //make a list of collections out of two geometries - private static List wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) { - List list = new ArrayList(); - if (g1.geometryType() != OGCConcreteGeometryCollection.TYPE) { - g1 = new OGCConcreteGeometryCollection(g1, g1.getEsriSpatialReference()); - } - if (g2.geometryType() != OGCConcreteGeometryCollection.TYPE) { - g2 = new OGCConcreteGeometryCollection(g2, g2.getEsriSpatialReference()); - } - - list.add((OGCConcreteGeometryCollection)g1); - list.add((OGCConcreteGeometryCollection)g2); - - return list; - } /** * Checks if collection is flattened. * @return True for the flattened collection. A flattened collection contains up to three non-empty geometries: @@ -869,24 +864,23 @@ public OGCConcreteGeometryCollection flatten() { /** * Fixes topological overlaps in the GeometryCollecion. * This is equivalent to union of the geometry collection elements. - * + * + * TODO "flattened" collection is supposed to contain only mutli-geometries, but this method may return single geometries + * e.g. for GEOMETRYCOLLECTION (LINESTRING (...)) it returns GEOMETRYCOLLECTION (LINESTRING (...)) + * and not GEOMETRYCOLLECTION (MULTILINESTRING (...)) * @return A geometry collection that is flattened and has no overlapping elements. */ public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() { - ArrayList geoms = new ArrayList(); - + //flatten and crack/cluster GeometryCursor cursor = OGCStructureInternal.prepare_for_ops_(flatten().getEsriGeometryCursor(), esriSR); - for (Geometry g = cursor.next(); g != null; g = cursor.next()) { - geoms.add(g); - } - + //make sure geometries don't overlap - return removeOverlapsHelper_(geoms); + return new OGCConcreteGeometryCollection(removeOverlapsHelper_(toList(cursor)), esriSR); } - - private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms) { - ArrayList result = new ArrayList(); + + private GeometryCursor removeOverlapsHelper_(List geoms) { + List result = new ArrayList(); for (int i = 0; i < geoms.size(); ++i) { Geometry current = geoms.get(i); if (current.isEmpty()) @@ -905,7 +899,7 @@ private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms result.add(current); } - return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(result), esriSR); + return new SimpleGeometryCursor(result); } private static class FlatteningCollectionCursor extends GeometryCursor { @@ -956,36 +950,9 @@ public int getGeometryID() { //Collectively processes group of geometry collections (intersects all segments and clusters points). //Flattens collections, removes overlaps. //Once done, the result collections would work well for topological and relational operations. - private List prepare_for_ops_(List geoms) { - assert(geoms != null && !geoms.isEmpty()); - GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(new FlatteningCollectionCursor(geoms), esriSR); - - List result = new ArrayList(); - int prevCollectionIndex = -1; - List list = null; - for (Geometry g = prepared.next(); g != null; g = prepared.next()) { - int c = prepared.getGeometryID(); - if (c != prevCollectionIndex) { - //add empty collections for all skipped indices - for (int i = prevCollectionIndex; i < c - 1; i++) { - result.add(new OGCConcreteGeometryCollection(esriSR)); - } - - if (list != null) { - result.add(removeOverlapsHelper_(list)); - } - - list = new ArrayList(); - prevCollectionIndex = c; - } - - list.add(g); - } - - if (list != null) { - result.add(removeOverlapsHelper_(list)); - } - - return result; + private GeometryCursor prepare_for_ops_(OGCConcreteGeometryCollection collection) { + assert(collection != null && !collection.isEmpty()); + GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(collection.flatten().getEsriGeometryCursor(), esriSR); + return removeOverlapsHelper_(toList(prepared)); } } diff --git a/src/test/java/com/esri/core/geometry/TestOGCDisjoint.java b/src/test/java/com/esri/core/geometry/TestOGCDisjoint.java new file mode 100644 index 00000000..45090e83 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCDisjoint.java @@ -0,0 +1,126 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Test; + +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestOGCDisjoint +{ + @Test + public void testPoint() + { + // point + assertDisjoint("POINT (1 2)", "POINT (3 4)"); + assertDisjoint("POINT (1 2)", "POINT EMPTY"); + assertNotDisjoint("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); + + // multi-point + assertDisjoint("POINT (1 2)", "MULTIPOINT (3 4, 5 6)"); + assertDisjoint("POINT (1 2)", "MULTIPOINT EMPTY"); + assertNotDisjoint("POINT (1 2)", "MULTIPOINT (1 2, 3 4, 5 6)", "POINT (1 2)"); + assertNotDisjoint("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); + } + + @Test + public void testLinestring() + { + // TODO Fill in + } + + @Test + public void testPolygon() + { + // TODO Fill in + } + + @Test + public void testGeometryCollection() + { + assertDisjoint("GEOMETRYCOLLECTION (POINT (1 2))", "POINT (3 4)"); + // GeometryException: internal error + assertDisjoint("GEOMETRYCOLLECTION (POINT (1 2))", "POINT EMPTY"); + assertNotDisjoint("GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)", "POINT (1 2)"); + + assertDisjoint("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (0 0)"); + assertNotDisjoint("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (3 4)", "POINT (3 4)"); + + String wkt = "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 5 0), POLYGON ((2 2, 3 2, 3 3, 2 2)))"; + assertDisjoint(wkt, gc("POINT (0 2)")); + + assertNotDisjoint(wkt, gc("POINT (1 2)"), "POINT (1 2)"); + // point within the line + assertNotDisjoint(wkt, gc("POINT (0 0)"), "POINT (0 0)"); + assertNotDisjoint(wkt, gc("POINT (1 0)"), "POINT (1 0)"); + // point within the polygon + assertNotDisjoint(wkt, gc("POINT (2 2)"), "POINT (2 2)"); + assertNotDisjoint(wkt, gc("POINT (2.5 2)"), "POINT (2.5 2)"); + assertNotDisjoint(wkt, gc("POINT (2.5 2.1)"), "POINT (2.5 2.1)"); + + assertDisjoint(wkt, gc("LINESTRING (0 2, 1 3)")); + + // line intersects the point + assertNotDisjoint(wkt, gc("LINESTRING (0 1, 2 3)"), "POINT (1 2)"); + // line intersects the line + assertNotDisjoint(wkt, gc("LINESTRING (0 0, 1 0)"), "LINESTRING (0 0, 1 0)"); + assertNotDisjoint(wkt, gc("LINESTRING (5 -1, 5 1)"), "POINT (5 0)"); + // line intersects the polygon + assertNotDisjoint(wkt, gc("LINESTRING (0 0, 5 5)"), gc("POINT (0 0), LINESTRING (2 2, 3 3)")); + assertNotDisjoint(wkt, gc("LINESTRING (0 2.5, 2.6 2.5)"), "LINESTRING (2.5 2.5, 2.6 2.5)"); + + assertDisjoint(wkt, gc("POLYGON ((5 5, 6 5, 6 6, 5 5))")); + assertDisjoint(wkt, gc("POLYGON ((-1 -1, 10 -1, 10 10, -1 10, -1 -1), (-0.1 -0.1, 5.1 -0.1, 5.1 5.1, -0.1 5.1, -0.1 -0.1))")); + + assertNotDisjoint(wkt, gc("POLYGON ((-1 -1, 10 -1, 10 10, -1 10, -1 -1))"), gc("POINT (1 2), LINESTRING (0 0, 5 0), POLYGON ((2 2, 3 2, 3 3, 2 2))")); + assertNotDisjoint(wkt, gc("POLYGON ((2 -1, 4 -1, 4 1, 2 1, 2 -1))"), "LINESTRING (2 0, 4 0)"); + assertNotDisjoint(wkt, gc("POLYGON ((0 1, 1.5 1, 1.5 2.5, 0 2.5, 0 1))"), "POINT (1 2)"); + assertNotDisjoint(wkt, gc("POLYGON ((5 0, 6 0, 6 5, 5 0))"), "POINT (5 0)"); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } + + private void assertDisjoint(String wkt, String otherWkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + assertTrue(geometry.disjoint(otherGeometry)); + assertFalse(geometry.intersects(otherGeometry)); + assertTrue(geometry.intersection(otherGeometry).isEmpty()); + + assertTrue(otherGeometry.disjoint(geometry)); + assertFalse(otherGeometry.intersects(geometry)); + assertTrue(otherGeometry.intersection(geometry).isEmpty()); + } + + private void assertNotDisjoint(String wkt, String otherWkt, String intersectionWkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + assertFalse(geometry.disjoint(otherGeometry)); + assertTrue(geometry.intersects(otherGeometry)); + assertEquals(intersectionWkt, geometry.intersection(otherGeometry).asText()); + + assertFalse(otherGeometry.disjoint(geometry)); + assertTrue(otherGeometry.intersects(geometry)); + assertEquals(intersectionWkt, otherGeometry.intersection(geometry).asText()); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java index 50832f2d..8a98d5d1 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java @@ -14,9 +14,9 @@ package com.esri.core.geometry; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import com.esri.core.geometry.ogc.OGCGeometry; import org.junit.Test; +import static com.esri.core.geometry.ogc.OGCGeometry.fromText; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -35,7 +35,7 @@ public void test() private void assertFlatten(String wkt, String flattenedWkt) { - OGCConcreteGeometryCollection collection = (OGCConcreteGeometryCollection) OGCGeometry.fromText(wkt); + OGCConcreteGeometryCollection collection = (OGCConcreteGeometryCollection) fromText(wkt); assertEquals(flattenedWkt, collection.flatten().asText()); assertTrue(collection.flatten().isFlattened()); assertEquals(flattenedWkt, collection.flatten().flatten().asText()); From 7b2bc6af4f50fea99ce9ccea252b1547f5a3cf78 Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Fri, 8 Jun 2018 01:10:08 -0400 Subject: [PATCH 075/116] Block overlaps and symDifference for geometry collections --- .../java/com/esri/core/geometry/ogc/OGCGeometry.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 87e8c620..17ef2f8f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -329,7 +329,8 @@ public boolean contains(OGCGeometry another) { public boolean overlaps(OGCGeometry another) { if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { - return another.overlaps(this); //overlaps should be symmetric + // TODO + throw new UnsupportedOperationException(); } com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); @@ -530,9 +531,11 @@ public OGCGeometry difference(OGCGeometry another) { } public OGCGeometry symDifference(OGCGeometry another) { - if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) - return another.symDifference(this); - + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + // TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return createFromEsriGeometry( From 6fcc5e68237dc2cca710ee594d8ab8e9c69fa1a3 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Tue, 26 Jun 2018 16:36:29 -0700 Subject: [PATCH 076/116] update jackson to 2.9.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0255ec18..3035ef99 100755 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ 1.6 - 2.9.4 + 2.9.6 4.12 0.9 From ac760efd95d14d5fb8550f1970c8a886df66b171 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Wed, 27 Jun 2018 08:19:56 -0700 Subject: [PATCH 077/116] jackson-2.9.6 --- build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.xml b/build.xml index a4bc51b1..a2e894a4 100644 --- a/build.xml +++ b/build.xml @@ -14,7 +14,7 @@ - + From c74e7a8fb604bf1831e0982f71e5ee67e692354a Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 29 Jun 2018 09:01:15 -0700 Subject: [PATCH 078/116] Geometry release v2.2.0 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46b42a05..a4b5e0ae 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.1.0 + 2.2.0 ``` diff --git a/pom.xml b/pom.xml index 3035ef99..e4c2b890 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.0-SNAPSHOT + 2.2.0 jar Esri Geometry API for Java From e7fb7cae3468c14d33dad528f7dfe6b08d4048e2 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 6 Jul 2018 10:56:30 -0700 Subject: [PATCH 079/116] Add serialization for QuadTree and related classes --- .../core/geometry/AttributeStreamOfInt32.java | 58 ++++++++++- .../java/com/esri/core/geometry/QuadTree.java | 8 +- .../com/esri/core/geometry/QuadTreeImpl.java | 78 +++++++++++++-- .../geometry/StridedIndexTypeCollection.java | 8 +- .../esri/core/geometry/TestSerialization.java | 96 ++++++++++++++++++- 5 files changed, 232 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 1939bb0f..52d07517 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,14 +27,20 @@ import com.esri.core.geometry.VertexDescription.Persistence; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.nio.ByteBuffer; +import java.nio.IntBuffer; import java.util.Arrays; import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32; import static com.esri.core.geometry.SizeOf.sizeOfIntArray; -final class AttributeStreamOfInt32 extends AttributeStreamBase { - private int[] m_buffer = null; +final class AttributeStreamOfInt32 extends AttributeStreamBase implements Serializable { + private static final long serialVersionUID = 1L; + + transient private int[] m_buffer = null; private int m_size; public void reserve(int reserve) @@ -594,7 +600,6 @@ private void _selfWriteRangeImpl(int toElement, int count, int fromElement, // reverse what we written int j = toElement; int offset = toElement + count - stride; - int dj = stride; for (int i = 0, n = count / 2; i < n; i++) { for (int k = 0; k < stride; k++) { int v = m_buffer[j + k]; @@ -728,4 +733,49 @@ void quicksort(int leftIn, int rightIn, IntComparator compare, public void sort(int start, int end) { Arrays.sort(m_buffer, start, end); } + + private void writeObject(java.io.ObjectOutputStream stream) + throws IOException { + stream.defaultWriteObject(); + IntBuffer intBuf = null; + byte[] bytes = null; + for (int i = 0; i < m_size;) { + int n = Math.min(32, m_size - i); + if (bytes == null) { + bytes = new byte[n * 4]; //32 elements at a time + ByteBuffer buf = ByteBuffer.wrap(bytes); + intBuf = buf.asIntBuffer(); + } + intBuf.rewind(); + intBuf.put(m_buffer, i, n); + stream.write(bytes, 0, n * 4); + i += n; + } + } + + private void readObject(java.io.ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + m_buffer = new int[m_size]; + IntBuffer intBuf = null; + byte[] bytes = null; + for (int i = 0; i < m_size;) { + int n = Math.min(32, m_size - i); + if (bytes == null) { + bytes = new byte[n * 4]; //32 elements at a time + ByteBuffer buf = ByteBuffer.wrap(bytes); + intBuf = buf.asIntBuffer(); + } + stream.read(bytes, 0, n * 4); + intBuf.rewind(); + intBuf.get(m_buffer, i, n); + i += n; + } + } + + @SuppressWarnings("unused") + private void readObjectNoData() throws ObjectStreamException { + m_buffer = new int[2]; + m_size = 0; + } } diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index 32d81c3c..0b4a4945 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,7 +25,11 @@ package com.esri.core.geometry; -public class QuadTree { +import java.io.Serializable; + +public class QuadTree implements Serializable { + private static final long serialVersionUID = 1L; + public static final class QuadTreeIterator { /** * Resets the iterator to an starting state on the QuadTree. If the diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index fe48999a..bedabdac 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,9 +23,15 @@ */ package com.esri.core.geometry; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.util.ArrayList; -class QuadTreeImpl { +class QuadTreeImpl implements Serializable { + private static final long serialVersionUID = 1L; + static final class QuadTreeIteratorImpl { /** * Resets the iterator to an starting state on the Quad_tree_impl. If @@ -1248,19 +1254,28 @@ private void set_data_values_(int data_handle, int element, Envelope2D bounding_ private Envelope2D m_data_extent; private StridedIndexTypeCollection m_quad_tree_nodes; private StridedIndexTypeCollection m_element_nodes; - private ArrayList m_data; + transient private ArrayList m_data; private AttributeStreamOfInt32 m_free_data; private int m_root; private int m_height; private boolean m_b_store_duplicates; - private int m_quadrant_mask = 3; - private int m_height_bit_shift = 2; - private int m_flushing_count = 5; + final static private int m_quadrant_mask = 3; + final static private int m_height_bit_shift = 2; + final static private int m_flushing_count = 5; static final class Data { int element; Envelope2D box; + + Data() { + } + + Data(int element_, double x1, double y1, double x2, double y2) { + element = element_; + box = new Envelope2D(); + box.setCoords(x1, y1, x2, y2); + } Data(int element_, Envelope2D box_) { element = element_; @@ -1269,6 +1284,57 @@ static final class Data { } } + private void writeObject(java.io.ObjectOutputStream stream) + throws IOException { + stream.defaultWriteObject(); + stream.writeInt(m_data.size()); + for (int i = 0, n = m_data.size(); i < n; ++i) { + Data d = m_data.get(i); + if (d != null) { + stream.writeByte(1); + stream.writeInt(d.element); + stream.writeDouble(d.box.xmin); + stream.writeDouble(d.box.ymin); + stream.writeDouble(d.box.xmax); + stream.writeDouble(d.box.ymax); + } + else { + stream.writeByte(0); + } + + } + } + + private void readObject(java.io.ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int dataSize = stream.readInt(); + m_data = new ArrayList(dataSize); + for (int i = 0, n = dataSize; i < n; ++i) { + int b = stream.readByte(); + if (b == 1) { + int elm = stream.readInt(); + double x1 = stream.readDouble(); + double y1 = stream.readDouble(); + double x2 = stream.readDouble(); + double y2 = stream.readDouble(); + Data d = new Data(elm, x1, y1, x2, y2); + m_data.add(d); + } + else if (b == 0) { + m_data.add(null); + } + else { + throw new IOException(); + } + } + } + + @SuppressWarnings("unused") + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Stream data required"); + } + /* m_quad_tree_nodes * 0: m_north_east_child * 1: m_north_west_child diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index c54cfe9a..8bd607e5 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,13 +23,17 @@ */ package com.esri.core.geometry; +import java.io.Serializable; + /** * A collection of strides of Index_type elements. To be used when one needs a * collection of homogeneous elements that contain only integer fields (i.e. * structs with Index_type members) Recycles the strides. Allows for constant * time creation and deletion of an element. */ -final class StridedIndexTypeCollection { +final class StridedIndexTypeCollection implements Serializable { + private static final long serialVersionUID = 1L; + private int[][] m_buffer = null; private int m_firstFree = -1; private int m_last = 0; diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 8cdb4ad9..5f3796a1 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -401,4 +400,97 @@ public void testSerializeEnvelope2D() { } } + public void testAttributeStreamOfInt32() { + AttributeStreamOfInt32 a = new AttributeStreamOfInt32(0); + for (int i = 0; i < 100; i++) + a.add(i); + + try { + // serialize + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(baos); + os.writeObject(a); + os.close(); + baos.close(); + + // deserialize + ByteArrayInputStream bais = new ByteArrayInputStream( + baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + AttributeStreamOfInt32 aOut = (AttributeStreamOfInt32) in.readObject(); + in.close(); + bais.close(); + + assertTrue(aOut.size() == a.size()); + for (int i = 0; i < 100; i++) + assertTrue(aOut.get(i) == a.get(i)); + + } catch (Exception e) { + fail("AttributeStreamOfInt32 serialization failure"); + } + + } + + @Test + public void testQuadTree() { + MultiPoint mp = new MultiPoint(); + int r = 124124; + for (int i = 0; i < 100; ++i) { + r = NumberUtils.nextRand(r); + int x = r; + r = NumberUtils.nextRand(r); + int y = r; + mp.add(x, y); + } + + Envelope2D extent = new Envelope2D(); + mp.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + Envelope2D boundingbox = new Envelope2D(); + Point2D pt; + + for (int i = 0; i < mp.getPointCount(); i++) { + pt = mp.getXY(i); + boundingbox.setCoords(pt.x, pt.y, pt.x, pt.y); + quadTree.insert(i, boundingbox, -1); + } + + try { + // serialize + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(baos); + os.writeObject(quadTree); + os.close(); + baos.close(); + + // deserialize + ByteArrayInputStream bais = new ByteArrayInputStream( + baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + QuadTree qOut = (QuadTree) in.readObject(); + in.close(); + bais.close(); + + assertTrue(quadTree.getElementCount() == qOut.getElementCount()); + QuadTree.QuadTreeIterator iter1 = quadTree.getIterator(); + QuadTree.QuadTreeIterator iter2 = qOut.getIterator(); + int h1 = iter1.next(); + int h2 = iter2.next(); + for (; h1 != -1 && h2 != -1; h1 = iter1.next(), h2 = iter2.next()) { + assertTrue(quadTree.getElement(h1) == qOut.getElement(h2)); + assertTrue(quadTree.getElementExtent(h1).equals(qOut.getElementExtent(h2))); + assertTrue(quadTree.getExtent(quadTree.getQuad(h1)).equals(qOut.getExtent(qOut.getQuad(h2)))); + int c1 = quadTree.getSubTreeElementCount(quadTree.getQuad(h1)); + int c2 = qOut.getSubTreeElementCount(qOut.getQuad(h2)); + assertTrue(c1 == c2); + } + + assertTrue(h1 == -1 && h2 == -1); + + assertTrue(quadTree.getDataExtent().equals(qOut.getDataExtent())); + } catch (Exception e) { + fail("QuadTree serialization failure"); + } + + } } From acc586158532658c73a43bdfeec5a75ee8bab34d Mon Sep 17 00:00:00 2001 From: Tim Meehan Date: Wed, 1 Aug 2018 12:42:03 -0700 Subject: [PATCH 080/116] Fix NPE on estimateMemorySize against empty multipart geometries This commit fixes an NPE in the estimateMemorySize method when the following geometries are empty: - LINESTRING - MULTILINESTRING - GEOMETRY - MULTIGEOMETRY It also adds test "empty" versions the current unit test suite. --- .../com/esri/core/geometry/MultiPathImpl.java | 4 +-- .../core/geometry/TestEstimateMemorySize.java | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 54ec0a5d..249a2a48 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -66,8 +66,8 @@ public long estimateMemorySize() + (m_envelope != null ? m_envelope.estimateMemorySize() : 0) + (m_moveToPoint != null ? m_moveToPoint.estimateMemorySize() : 0) + (m_cachedRingAreas2D != null ? m_cachedRingAreas2D.estimateMemorySize() : 0) - + m_paths.estimateMemorySize() - + m_pathFlags.estimateMemorySize() + + (m_paths != null ? m_paths.estimateMemorySize() : 0) + + (m_pathFlags != null ? m_pathFlags.estimateMemorySize() : 0) + (m_segmentFlags != null ? m_segmentFlags.estimateMemorySize() : 0) + (m_segmentParamIndex != null ? m_segmentParamIndex.estimateMemorySize() : 0) + (m_segmentParams != null ? m_segmentParams.estimateMemorySize() : 0); diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index e4195c58..ae391d8c 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -77,36 +77,71 @@ public void testPoint() { testGeometry(parseWkt("POINT (1 2)")); } + @Test + public void testEmptyPoint() { + testGeometry(parseWkt("POINT EMPTY")); + } + @Test public void testMultiPoint() { testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); } + @Test + public void testEmptyMultiPoint() { + testGeometry(parseWkt("MULTIPOINT EMPTY")); + } + @Test public void testLineString() { testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); } + @Test + public void testEmptyLineString() { + testGeometry(parseWkt("LINESTRING EMPTY")); + } + @Test public void testMultiLineString() { testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); } + @Test + public void testEmptyMultiLineString() { + testGeometry(parseWkt("MULTILINESTRING EMPTY")); + } + @Test public void testPolygon() { testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); } + @Test + public void testEmptyPolygon() { + testGeometry(parseWkt("POLYGON EMPTY")); + } + @Test public void testMultiPolygon() { testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); } + @Test + public void testEmptyMultiPolygon() { + testGeometry(parseWkt("MULTIPOLYGON EMPTY")); + } + @Test public void testGeometryCollection() { testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); } + @Test + public void testEmptyGeometryCollection() { + testGeometry(parseWkt("GEOMETRYCOLLECTION EMPTY")); + } + private void testGeometry(OGCGeometry geometry) { assertTrue(geometry.estimateMemorySize() > 0); } From 072434b521ad1dd62ef6d9ee2214888a1868e5a3 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 14 Aug 2018 13:43:22 -0700 Subject: [PATCH 081/116] Fix convex hull crash for collection of polygons --- .gitignore | 1 + .../geometry/ogc/OGCConcreteGeometryCollection.java | 4 ++-- .../java/com/esri/core/geometry/TestConvexHull.java | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7328fe5d..1e2db53a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ ehthumbs.db Thumbs.db target/* /bin/ +/target/ diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 155b8c41..66eb1310 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -444,13 +444,13 @@ else if (geom.getType() == Geometry.Type.Point) { } if (!polygon.isEmpty()) { - if (!resultGeom.isEmpty()) { + if (resultGeom != null && !resultGeom.isEmpty()) { Geometry[] geoms = { resultGeom, polygon }; resultGeom = OperatorConvexHull.local().execute( new SimpleGeometryCursor(geoms), true, null).next(); } else { - resultGeom = polygon; + resultGeom = OperatorConvexHull.local().execute(polygon, null); } } diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index b29d3aaf..ee9c764e 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -1059,4 +1059,14 @@ public void testHullIssueGithub172() { } } + @Test + public void testHullIssueGithub194() { + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POLYGON((0 0, 1 0, 1 1, 0 0)), POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POLYGON ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))") == 0); + } + } } From 28666569726a3b45184180ba3398219eab653f06 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 17 Aug 2018 13:19:11 -0700 Subject: [PATCH 082/116] Delete MgrsConversionMode.java This file is unused. --- .../core/geometry/MgrsConversionMode.java | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 src/main/java/com/esri/core/geometry/MgrsConversionMode.java diff --git a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java deleted file mode 100644 index b92ef646..00000000 --- a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -/** - * The modes for converting between military grid reference system (MGRS) - * notation and coordinates. - * */ -public interface MgrsConversionMode { - /** - * Uses the spheroid to determine the military grid string. - */ - public static final int mgrsAutomatic = 0;// PE_MGRS_STYLE_AUTO - /** - * Treats all spheroids as new, like WGS 1984, when creating or reading a - * military grid string. The 180 longitude falls into zone 60. - */ - public static final int mgrsNewStyle = 0x100; // PE_MGRS_STYLE_NEW - /** - * Treats all spheroids as old, like Bessel 1841, when creating or reading a - * military grid string. The 180 longitude falls into zone 60. - */ - public static final int mgrsOldStyle = 0x200; // PE_MGRS_STYLE_OLD - /** - * Treats all spheroids as new, like WGS 1984, when creating or reading a - * military grid string. The 180 longitude falls into zone 01. - */ - public static final int mgrsNewWith180InZone01 = 0x1000 + 0x100; // PE_MGRS_180_ZONE_1_PLUS - // | - // PE_MGRS_STYLE_NEW - /** - * Treats all spheroids as old, like Bessel 1841, when creating or reading a - * military grid string. The 180 longitude falls into zone 01. - */ - public static final int mgrsOldWith180InZone01 = 0x1000 + 0x200; // PE_MGRS_180_ZONE_1_PLUS - // | - // PE_MGRS_STYLE_OLD -} From bc4769aa254e82a33b0959abc15ff146886ce932 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 31 Aug 2018 09:36:43 -0700 Subject: [PATCH 083/116] Geometry release v2.2.1 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4b5e0ae..171fa4c6 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.0 + 2.2.1 ``` diff --git a/pom.xml b/pom.xml index e4c2b890..2815d47f 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.0 + 2.2.1 jar Esri Geometry API for Java From 17fdf4e57264fdd778e3d87e59566d51e6ce923a Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Wed, 21 Nov 2018 14:46:25 -0500 Subject: [PATCH 084/116] Add accelerator size to OGCGeometry#estimateMemorySize --- .../core/geometry/GeometryAccelerators.java | 9 ++- .../com/esri/core/geometry/MultiPathImpl.java | 5 ++ .../com/esri/core/geometry/QuadTreeImpl.java | 23 +++++++- .../core/geometry/RasterizedGeometry2D.java | 6 ++ .../geometry/RasterizedGeometry2DImpl.java | 31 +++++++--- .../esri/core/geometry/SimpleRasterizer.java | 59 ++++++++++++++++--- .../java/com/esri/core/geometry/SizeOf.java | 23 ++++++++ .../geometry/StridedIndexTypeCollection.java | 18 ++++++ .../esri/core/geometry/Transformation2D.java | 6 ++ .../core/geometry/TestEstimateMemorySize.java | 47 +++++++++++++-- 10 files changed, 201 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 2ccc84cc..90d699d0 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -23,8 +23,6 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; - class GeometryAccelerators { private RasterizedGeometry2D m_rasterizedGeometry; @@ -84,4 +82,11 @@ static boolean canUseQuadTreeForPaths(Geometry geom) { return true; } + + public long estimateMemorySize() + { + return (m_rasterizedGeometry != null ? m_rasterizedGeometry.estimateMemorySize() : 0) + + (m_quad_tree != null ? m_quad_tree.estimateMemorySize() : 0) + + (m_quad_tree_for_paths != null ? m_quad_tree_for_paths.estimateMemorySize() : 0); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 249a2a48..c9400ee0 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -77,6 +77,11 @@ public long estimateMemorySize() size += m_vertexAttributes[i].estimateMemorySize(); } } + + if (m_accelerators != null) { + size += m_accelerators.estimateMemorySize(); + } + return size; } diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index bedabdac..e9234bb5 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -29,6 +29,10 @@ import java.io.Serializable; import java.util.ArrayList; +import static com.esri.core.geometry.SizeOf.SIZE_OF_DATA; +import static com.esri.core.geometry.SizeOf.SIZE_OF_QUAD_TREE_IMPL; +import static com.esri.core.geometry.SizeOf.sizeOfObjectArray; + class QuadTreeImpl implements Serializable { private static final long serialVersionUID = 1L; @@ -777,6 +781,22 @@ QuadTreeSortedIteratorImpl getSortedIterator() { return new QuadTreeSortedIteratorImpl(getIterator()); } + public long estimateMemorySize() + { + long size = SIZE_OF_QUAD_TREE_IMPL + + (m_extent != null ? m_extent.estimateMemorySize() : 0) + + (m_data_extent != null ? m_data_extent.estimateMemorySize() : 0) + + (m_quad_tree_nodes != null ? m_quad_tree_nodes.estimateMemorySize() : 0) + + (m_element_nodes != null ? m_element_nodes.estimateMemorySize() : 0) + + (m_free_data != null ? m_free_data.estimateMemorySize() : 0); + + if (m_data != null) { + size += sizeOfObjectArray(m_data.size()) + m_data.size() * SIZE_OF_DATA; + } + + return size; + } + private void reset_(Envelope2D extent, int height) { if (height < 0 || height > 127) throw new IllegalArgumentException("invalid height"); @@ -1268,9 +1288,6 @@ static final class Data { int element; Envelope2D box; - Data() { - } - Data(int element_, double x1, double y1, double x2, double y2) { element = element_; box = new Envelope2D(); diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 46ccd5c6..ff1b8257 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -136,4 +136,10 @@ static boolean canUseAccelerator(Geometry geom) { */ public abstract boolean dbgSaveToBitmap(String fileName); + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); } diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index ff21c8ec..6b9a2e4d 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -24,18 +24,14 @@ package com.esri.core.geometry; -import java.io.*; +import java.io.FileOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import com.esri.core.geometry.Envelope2D; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryException; -import com.esri.core.geometry.NumberUtils; -import com.esri.core.geometry.Point2D; -import com.esri.core.geometry.Segment; -import com.esri.core.geometry.SegmentIteratorImpl; -import com.esri.core.geometry.SimpleRasterizer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL; +import static com.esri.core.geometry.SizeOf.SIZE_OF_SCAN_CALLBACK_IMPL; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; final class RasterizedGeometry2DImpl extends RasterizedGeometry2D { int[] m_bitmap; @@ -89,6 +85,13 @@ public void drawScan(int[] scans, int scanCount3) { } } } + + @Override + public long estimateMemorySize() + { + return SIZE_OF_SCAN_CALLBACK_IMPL + + (m_bitmap != null ? sizeOfIntArray(m_bitmap.length) : 0); + } } void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPathImpl polygon, boolean isWinding) { @@ -559,4 +562,14 @@ public boolean dbgSaveToBitmap(String fileName) { } } + + @Override + public long estimateMemorySize() + { + return SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL + + (m_geomEnv != null ? m_geomEnv.estimateMemorySize() : 0) + + (m_transform != null ? m_transform.estimateMemorySize(): 0) + + (m_rasterizer != null ? m_rasterizer.estimateMemorySize(): 0) + + (m_callback != null ? m_callback.estimateMemorySize(): 0); + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index de817443..d91f734f 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -24,11 +24,14 @@ package com.esri.core.geometry; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; +import static com.esri.core.geometry.SizeOf.SIZE_OF_EDGE; +import static com.esri.core.geometry.SizeOf.SIZE_OF_SIMPLE_RASTERIZER; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; +import static com.esri.core.geometry.SizeOf.sizeOfObjectArray; + /** * Simple scanline rasterizer. Caller provides a callback to draw pixels to actual surface. * @@ -44,15 +47,22 @@ public class SimpleRasterizer { * Winding fill rule */ public final static int WINDING = 1; - - public static interface ScanCallback { + + public interface ScanCallback { /** * Rasterizer calls this method for each scan it produced * @param scans array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ - public abstract void drawScan(int[] scans, int scanCount3); + void drawScan(int[] scans, int scanCount3); + + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ + long estimateMemorySize(); } public SimpleRasterizer() { @@ -340,17 +350,50 @@ final boolean addSegmentStroke(double x1, double y1, double x2, double y2, doubl } public final ScanCallback getScanCallback() { return callback_; } - - + + public long estimateMemorySize() + { + // callback_ is only a pointer, the actual size is accounted for in the caller of setup() + long size = SIZE_OF_SIMPLE_RASTERIZER + + (activeEdgesTable_ != null ? activeEdgesTable_.estimateMemorySize() : 0) + + (scanBuffer_ != null ? sizeOfIntArray(scanBuffer_.length) : 0); + + if (ySortedEdges_ != null) { + size += sizeOfObjectArray(ySortedEdges_.length); + for (int i = 0; i < ySortedEdges_.length; i++) { + if (ySortedEdges_[i] != null) { + size += ySortedEdges_[i].estimateMemorySize(); + } + } + } + + if (sortBuffer_ != null) { + size += sizeOfObjectArray(sortBuffer_.length); + for (int i = 0; i < sortBuffer_.length; i++) { + if (sortBuffer_[i] != null) { + size += sortBuffer_[i].estimateMemorySize(); + } + } + } + + return size; + } + //PRIVATE - private static class Edge { + static class Edge { long x; long dxdy; int y; int ymax; int dir; Edge next; + + long estimateMemorySize() + { + // next is only a pointer, the actual size is accounted for in SimpleRasterizer#estimateMemorySize + return SIZE_OF_EDGE; + } } private final void advanceAET_() { diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 31460366..6b097dad 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -36,6 +36,8 @@ import static sun.misc.Unsafe.ARRAY_INT_INDEX_SCALE; import static sun.misc.Unsafe.ARRAY_LONG_BASE_OFFSET; import static sun.misc.Unsafe.ARRAY_LONG_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_OBJECT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_OBJECT_INDEX_SCALE; import static sun.misc.Unsafe.ARRAY_SHORT_BASE_OFFSET; import static sun.misc.Unsafe.ARRAY_SHORT_INDEX_SCALE; @@ -88,6 +90,22 @@ public final class SizeOf { public static final int SIZE_OF_MAPGEOMETRY = 24; + public static final int SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL = 112; + + public static final int SIZE_OF_SCAN_CALLBACK_IMPL = 32; + + public static final int SIZE_OF_TRANSFORMATION_2D = 64; + + public static final int SIZE_OF_SIMPLE_RASTERIZER = 64; + + public static final int SIZE_OF_EDGE = 48; + + public static final int SIZE_OF_QUAD_TREE_IMPL = 48; + + public static final int SIZE_OF_DATA = 24; + + public static final int SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION = 48; + public static long sizeOfByteArray(int length) { return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); } @@ -116,6 +134,11 @@ public static long sizeOfDoubleArray(int length) { return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); } + public static long sizeOfObjectArray(int length) + { + return ARRAY_OBJECT_BASE_OFFSET + (((long) ARRAY_OBJECT_INDEX_SCALE) * length); + } + private SizeOf() { } } diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 8bd607e5..ae1f4dca 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -25,6 +25,10 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; +import static com.esri.core.geometry.SizeOf.sizeOfObjectArray; + /** * A collection of strides of Index_type elements. To be used when one needs a * collection of homogeneous elements that contain only integer fields (i.e. @@ -277,4 +281,18 @@ private void grow_(long newsize) { } } } + + public long estimateMemorySize() + { + long size = SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION; + if (m_buffer != null) { + size += sizeOfObjectArray(m_buffer.length); + for (int i = 0; i< m_buffer.length; i++) { + if (m_buffer[i] != null) { + size += sizeOfIntArray(m_buffer[i].length); + } + } + } + return size; + } } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index 99ea7cde..1704628a 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -24,6 +24,8 @@ package com.esri.core.geometry; +import static com.esri.core.geometry.SizeOf.SIZE_OF_TRANSFORMATION_2D; + /** * The affine transformation class for 2D. * @@ -921,4 +923,8 @@ public void extractScaleTransform(Transformation2D scale, rotateNshearNshift.multiply(this); } + public long estimateMemorySize() + { + return SIZE_OF_TRANSFORMATION_2D; + } } diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index ae391d8c..b1b40b22 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import com.esri.core.geometry.ogc.OGCGeometry; import com.esri.core.geometry.ogc.OGCLineString; @@ -66,6 +67,14 @@ public void testInstanceSizes() { assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); + assertEquals(getInstanceSize(RasterizedGeometry2DImpl.class), SizeOf.SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL); + assertEquals(getInstanceSize(RasterizedGeometry2DImpl.ScanCallbackImpl.class), SizeOf.SIZE_OF_SCAN_CALLBACK_IMPL); + assertEquals(getInstanceSize(Transformation2D.class), SizeOf.SIZE_OF_TRANSFORMATION_2D); + assertEquals(getInstanceSize(SimpleRasterizer.class), SizeOf.SIZE_OF_SIMPLE_RASTERIZER); + assertEquals(getInstanceSize(SimpleRasterizer.Edge.class), SizeOf.SIZE_OF_EDGE); + assertEquals(getInstanceSize(QuadTreeImpl.class), SizeOf.SIZE_OF_QUAD_TREE_IMPL); + assertEquals(getInstanceSize(QuadTreeImpl.Data.class), SizeOf.SIZE_OF_DATA); + assertEquals(getInstanceSize(StridedIndexTypeCollection.class), SizeOf.SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION); } private static long getInstanceSize(Class clazz) { @@ -93,7 +102,7 @@ public void testEmptyMultiPoint() { } @Test - public void testLineString() { + public void testAcceleratedGeometry() { testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); } @@ -104,7 +113,7 @@ public void testEmptyLineString() { @Test public void testMultiLineString() { - testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); + testAcceleratedGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); } @Test @@ -114,7 +123,7 @@ public void testEmptyMultiLineString() { @Test public void testPolygon() { - testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); + testAcceleratedGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); } @Test @@ -124,7 +133,7 @@ public void testEmptyPolygon() { @Test public void testMultiPolygon() { - testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); + testAcceleratedGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); } @Test @@ -146,6 +155,36 @@ private void testGeometry(OGCGeometry geometry) { assertTrue(geometry.estimateMemorySize() > 0); } + private void testAcceleratedGeometry(OGCGeometry geometry) { + long initialSize = geometry.estimateMemorySize(); + assertTrue(initialSize > 0); + + Envelope envelope = new Envelope(); + geometry.getEsriGeometry().queryEnvelope(envelope); + + long withEnvelopeSize = geometry.estimateMemorySize(); + assertTrue(withEnvelopeSize > initialSize); + + accelerate(geometry, GeometryAccelerationDegree.enumMild); + long mildAcceleratedSize = geometry.estimateMemorySize(); + assertTrue(mildAcceleratedSize > withEnvelopeSize); + + accelerate(geometry, GeometryAccelerationDegree.enumMedium); + long mediumAcceleratedSize = geometry.estimateMemorySize(); + assertTrue(mediumAcceleratedSize > mildAcceleratedSize); + + accelerate(geometry, GeometryAccelerationDegree.enumHot); + long hotAcceleratedSize = geometry.estimateMemorySize(); + assertTrue(hotAcceleratedSize > mediumAcceleratedSize); + } + + private void accelerate(OGCGeometry geometry, GeometryAccelerationDegree accelerationDegree) + { + Operator relateOperator = OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Relate); + boolean accelerated = relateOperator.accelerateGeometry(geometry.getEsriGeometry(), geometry.getEsriSpatialReference(), accelerationDegree); + assertTrue(accelerated); + } + private static OGCGeometry parseWkt(String wkt) { OGCGeometry geometry = OGCGeometry.fromText(wkt); geometry.setSpatialReference(null); From 937ca24b82ddce87a66948c5e8652ab64a83aa4d Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 30 Nov 2018 11:11:01 -0800 Subject: [PATCH 085/116] Fix rasterization with degenerate segments (#207) * Fix rasterization with degenerate segments * Change javadoc source version to 1.6 --- .gitignore | 2 + build.xml | 4 +- .../geometry/RasterizedGeometry2DImpl.java | 26 +++--- .../esri/core/geometry/SimpleRasterizer.java | 89 ++++++++++--------- .../esri/core/geometry/TestOGCContains.java | 14 +++ 5 files changed, 77 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 1e2db53a..4f5d1e04 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ bin/ results/ javadoc/ +depfiles/ +DepFiles/ esri-geometry-api.jar .project .classpath diff --git a/build.xml b/build.xml index a2e894a4..49f70b58 100644 --- a/build.xml +++ b/build.xml @@ -86,14 +86,14 @@ - + - + diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index 6b9a2e4d..c6bf1f8d 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -141,10 +141,6 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); double strokeHalfWidth = m_transform.transform(tol) + 1.5; - double shortSegment = 0.25; - Point2D vec = new Point2D(); - Point2D vecA = new Point2D(); - Point2D vecB = new Point2D(); Point2D ptStart = new Point2D(); Point2D ptEnd = new Point2D(); @@ -153,6 +149,7 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, double[] helper_xy_10_elm = new double[10]; Envelope2D segEnv = new Envelope2D(); Point2D ptOld = new Point2D(); + double extraWidth = 0; while (segIter.nextPath()) { boolean hasFan = false; boolean first = true; @@ -170,10 +167,11 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, if (hasFan) { rasterizer.startAddingEdges(); rasterizer.addSegmentStroke(prev_start.x, prev_start.y, - prev_end.x, prev_end.y, strokeHalfWidth, false, + prev_end.x, prev_end.y, strokeHalfWidth + extraWidth, false, helper_xy_10_elm); rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); hasFan = false; + extraWidth = 0.0; } first = true; @@ -195,19 +193,26 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, rasterizer.startAddingEdges(); hasFan = !rasterizer.addSegmentStroke(prev_start.x, - prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth + extraWidth, true, helper_xy_10_elm); rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); - if (!hasFan) + if (!hasFan) { ptOld.setCoords(prev_end); + extraWidth = 0.0; + } + else { + //track length of skipped segment to add it to the stroke width for the next edge. + extraWidth = Math.max(extraWidth, Point2D.distance(prev_start, prev_end)); + } } if (hasFan) { rasterizer.startAddingEdges(); hasFan = !rasterizer.addSegmentStroke(prev_start.x, - prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth + extraWidth, false, helper_xy_10_elm); rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + extraWidth = 0.0; } } } @@ -308,12 +313,10 @@ void init(MultiVertexGeometryImpl geom, double toleranceXY, m_transform = new Transformation2D(); m_transform.initializeFromRect(worldEnv, pixEnv);// geom to pixels - Transformation2D identityTransform = new Transformation2D(); - switch (geom.getType().value()) { case Geometry.GeometryType.MultiPoint: callback.setColor(m_rasterizer, 2); - fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); + fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); break; case Geometry.GeometryType.Polyline: callback.setColor(m_rasterizer, 2); @@ -545,7 +548,6 @@ public boolean dbgSaveToBitmap(String fileName) { // int32_t* rgb4 = (int32_t*)malloc(biSizeImage); for (int y = 0; y < height; y++) { int scanlineIn = y * ((width * 2 + 31) / 32); - int scanlineOut = offset + width * y; for (int x = 0; x < width; x++) { int res = (m_bitmap[scanlineIn + (x >> 4)] >> ((x & 15) * 2)) & 3; diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index d91f734f..a2e9dfa0 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -305,50 +305,51 @@ public final void fillEnvelope(Envelope2D envIn) { } } - final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, double[] helper_xy_10_elm) - { - double vec_x = x2 - x1; - double vec_y = y2 - y1; - double len = Math.sqrt(vec_x * vec_x + vec_y * vec_y); - if (skip_short && len < 0.5) - return false; - - boolean bshort = len < 0.00001; - if (bshort) - { - len = 0.00001; - vec_x = len; - vec_y = 0.0; - } - - double f = half_width / len; - vec_x *= f; vec_y *= f; - double vecA_x = -vec_y; - double vecA_y = vec_x; - double vecB_x = vec_y; - double vecB_y = -vec_x; - //extend by half width - x1 -= vec_x; - y1 -= vec_y; - x2 += vec_x; - y2 += vec_y; - //create rotated rectangle - double[] fan = helper_xy_10_elm; - assert(fan.length == 10); - fan[0] = x1 + vecA_x; - fan[1] = y1 + vecA_y;//fan[0].add(pt_start, vecA); - fan[2] = x1 + vecB_x; - fan[3] = y1 + vecB_y;//fan[1].add(pt_start, vecB); - fan[4] = x2 + vecB_x; - fan[5] = y2 + vecB_y;//fan[2].add(pt_end, vecB) - fan[6] = x2 + vecA_x; - fan[7] = y2 + vecA_y;//fan[3].add(pt_end, vecA) - fan[8] = fan[0]; - fan[9] = fan[1]; - addRing(fan); - return true; - } - + final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, + double[] helper_xy_10_elm) { + double vec_x = x2 - x1; + double vec_y = y2 - y1; + double sqr_len = vec_x * vec_x + vec_y * vec_y; + if (skip_short && sqr_len < (0.5 * 0.5)) { + return false; + } + + boolean veryShort = !skip_short && (sqr_len < (0.00001 * 0.00001)); + if (veryShort) { + vec_x = half_width + 0.00001; + vec_y = 0.0; + } else { + double f = half_width / Math.sqrt(sqr_len); + vec_x *= f; + vec_y *= f; + } + + double vecA_x = -vec_y; + double vecA_y = vec_x; + double vecB_x = vec_y; + double vecB_y = -vec_x; + // extend by half width + x1 -= vec_x; + y1 -= vec_y; + x2 += vec_x; + y2 += vec_y; + // create rotated rectangle + double[] fan = helper_xy_10_elm; + assert (fan.length == 10); + fan[0] = x1 + vecA_x; + fan[1] = y1 + vecA_y;// fan[0].add(pt_start, vecA); + fan[2] = x1 + vecB_x; + fan[3] = y1 + vecB_y;// fan[1].add(pt_start, vecB); + fan[4] = x2 + vecB_x; + fan[5] = y2 + vecB_y;// fan[2].add(pt_end, vecB) + fan[6] = x2 + vecA_x; + fan[7] = y2 + vecA_y;// fan[3].add(pt_end, vecA) + fan[8] = fan[0]; + fan[9] = fan[1]; + addRing(fan); + return true; + } + public final ScanCallback getScanCallback() { return callback_; } public long estimateMemorySize() diff --git a/src/test/java/com/esri/core/geometry/TestOGCContains.java b/src/test/java/com/esri/core/geometry/TestOGCContains.java index fd2c5116..04a328bf 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCContains.java +++ b/src/test/java/com/esri/core/geometry/TestOGCContains.java @@ -55,6 +55,20 @@ public void testGeometryCollection() { "GEOMETRYCOLLECTION (MULTIPOINT (0 0, 2 1))"); } + @Test + public void testAcceleratedPiP() { + String wkt = "MULTIPOLYGON (((-109.642707 30.5236901, -109.607932 30.5367411, -109.5820257 30.574184, -109.5728286 30.5874766, -109.568679 30.5934741, -109.5538097 30.5918356, -109.553714 30.5918251, -109.553289 30.596034, -109.550951 30.6191889, -109.5474935 30.6221179, -109.541059 30.6275689, -109.5373751 30.6326491, -109.522538 30.6531099, -109.514671 30.6611981, -109.456764 30.6548095, -109.4556456 30.6546861, -109.4536755 30.6544688, -109.4526481 30.6543554, -109.446824 30.6537129, -109.437751 30.6702901, -109.433968 30.6709781, -109.43338 30.6774591, -109.416243 30.7164651, -109.401643 30.7230741, -109.377583 30.7145241, -109.3487939 30.7073896, -109.348594 30.7073401, -109.3483718 30.7073797, -109.3477608 30.7074887, -109.3461903 30.7078834, -109.3451022 30.7081569, -109.3431732 30.7086416, -109.3423301 30.708844, -109.3419714 30.7089301, -109.3416347 30.709011, -109.3325693 30.7111874, -109.3323814 30.7112325, -109.332233 30.7112681, -109.332191 30.7112686, -109.3247809 30.7113581, -109.322215 30.7159391, -109.327776 30.7234381, -109.350134 30.7646001, -109.364505 30.8382481, -109.410211 30.8749199, -109.400048 30.8733419, -109.3847799 30.9652412, -109.3841625 30.9689575, -109.375268 31.0224939, -109.390544 31.0227899, -109.399749 31.0363341, -109.395787 31.0468411, -109.388174 31.0810249, -109.3912446 31.0891966, -109.3913452 31.0894644, -109.392735 31.0931629, -109.4000839 31.0979214, -109.402803 31.0996821, -109.4110458 31.1034586, -109.419153 31.1071729, -109.449782 31.1279489, -109.469654 31.1159979, -109.4734874 31.1131178, -109.473753 31.1129183, -109.4739754 31.1127512, -109.491296 31.0997381, -109.507789 31.0721811, -109.512776 31.0537519, -109.5271478 31.0606861, -109.5313703 31.0627234, -109.540698 31.0672239, -109.5805468 31.0674089, -109.5807399 31.0674209, -109.595423 31.0674779, -109.60347 31.0690241, -109.6048011 31.068808, -109.6050803 31.0687627, -109.6192237 31.0664664, -109.635432 31.0638349, -109.6520068 31.0955326, -109.6522294 31.0959584, -109.652373 31.0962329, -109.657709 31.0959719, -109.718258 31.0930099, -109.821036 31.0915909, -109.8183088 31.0793374, -109.8165128 31.0712679, -109.8140062 31.0600052, -109.8138512 31.0593089, -109.812707 31.0541679, -109.8188146 31.0531909, -109.8215447 31.0527542, -109.8436765 31.0492138, -109.8514316 31.0479733, -109.8620535 31.0462742, -109.8655958 31.0457076, -109.868388 31.0452609, -109.8795483 31.0359656, -109.909274 31.0112075, -109.9210382 31.0014092, -109.9216329 31.0009139, -109.920594 30.994183, -109.9195356 30.9873254, -109.9192113 30.9852243, -109.9186281 30.9814453, -109.917814 30.9761709, -109.933894 30.9748879, -109.94094 30.9768059, -109.944854 30.9719821, -109.950803 30.9702809, -109.954025 30.9652409, -109.9584129 30.9636033, -109.958471 30.9635809, -109.9590542 30.9644372, -109.959896 30.9656733, -109.9604184 30.9664405, -109.9606288 30.9667494, -109.9608462 30.9670686, -109.961225 30.9676249, -109.9611615 30.9702903, -109.9611179 30.9721175, -109.9610885 30.9733488, -109.9610882 30.9733604, -109.9610624 30.9744451, -109.961017 30.9763469, -109.962609 30.9786559, -109.9634437 30.9783167, -110.00172 30.9627641, -110.0021152 30.9627564, -110.0224353 30.9623622, -110.0365868 30.9620877, -110.037493 30.9620701, -110.0374055 30.961663, -110.033653 30.9442059, -110.0215506 30.9492932, -110.0180392 30.9507693, -110.011203 30.9536429, -110.0062891 30.9102124, -110.0058721 30.9065268, -110.004869 30.8976609, -109.996392 30.8957129, -109.985038 30.8870439, -109.969416 30.9006011, -109.967905 30.8687239, -109.903498 30.8447749, -109.882925 30.8458289, -109.865184 30.8206519, -109.86465 30.777698, -109.864515 30.7668429, -109.837007 30.7461781, -109.83453 30.7164469, -109.839017 30.7089009, -109.813394 30.6906529, -109.808694 30.6595701, -109.795334 30.6630041, -109.7943042 30.6427223, -109.7940456 30.6376287, -109.7940391 30.637501, -109.793823 30.6332449, -109.833511 30.6274289, -109.830299 30.6252799, -109.844198 30.6254801, -109.852442 30.6056949, -109.832973 30.6021201, -109.8050409 30.591211, -109.773847 30.5790279, -109.772859 30.5521999, -109.754427 30.5393969, -109.743293 30.5443401, -109.6966136 30.5417334, -109.6648181 30.5399578, -109.6560456 30.5394679, -109.6528439 30.5392912, -109.6504039 30.5391565, -109.6473602 30.5389885, -109.646906 30.5389634, -109.6414545 30.5386625, -109.639708 30.5385661, -109.6397729 30.5382443, -109.642707 30.5236901)))"; + String pointWkt = "POINT (-109.65 31.091666666673)"; + + OGCGeometry polygon = OGCGeometry.fromText(wkt); + OGCGeometry point = OGCGeometry.fromText(pointWkt); + assertTrue(polygon.contains(point)); + + OperatorContains.local() + .accelerateGeometry(polygon.getEsriGeometry(), null, Geometry.GeometryAccelerationDegree.enumMild); + assertTrue(polygon.contains(point));; + } + private void assertContains(String wkt, String otherWkt) { OGCGeometry geometry = OGCGeometry.fromText(wkt); OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); From fe623e815bad4fca9db6723bcd8bf0e1cf83e7ef Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 3 Dec 2018 14:53:30 -0500 Subject: [PATCH 086/116] Move ScanCallback#estimateMemorySize to implementation (#209) --- .../com/esri/core/geometry/RasterizedGeometry2DImpl.java | 6 +++++- src/main/java/com/esri/core/geometry/SimpleRasterizer.java | 7 ------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index c6bf1f8d..c7def2d4 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -86,7 +86,11 @@ public void drawScan(int[] scans, int scanCount3) { } } - @Override + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ public long estimateMemorySize() { return SIZE_OF_SCAN_CALLBACK_IMPL + diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index a2e9dfa0..8c6d7e46 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -56,13 +56,6 @@ public interface ScanCallback { * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ void drawScan(int[] scans, int scanCount3); - - /** - * Returns an estimate of this object size in bytes. - * - * @return Returns an estimate of this object size in bytes. - */ - long estimateMemorySize(); } public SimpleRasterizer() { From 7e51ba0757719bcf3ac4072da26aa261c8a9484f Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 3 Dec 2018 12:03:40 -0800 Subject: [PATCH 087/116] Geometry release v2.2.2 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 171fa4c6..01481e54 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.1 + 2.2.2 ``` diff --git a/pom.xml b/pom.xml index 2815d47f..e5907321 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.1 + 2.2.2 jar Esri Geometry API for Java From 494da8ec953d76e7c6072afbc081abfe48ff07cf Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Thu, 3 Jan 2019 10:10:28 -0800 Subject: [PATCH 088/116] v2.2.3 development & copyright year --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01481e54..d4ea84c7 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2018 Esri +Copyright 2013-2019 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pom.xml b/pom.xml index e5907321..eec05faa 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.2 + 2.2.3-SNAPSHOT jar Esri Geometry API for Java From 8070e1e24afa22396624e900388f31b7405bab11 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 30 Jul 2019 13:15:05 -0700 Subject: [PATCH 089/116] Fix formatting in OperatorCentroid (#226) * Fix formatting in OperatorCentroid * formatting in the unit test --- .../core/geometry/OperatorCentroid2D.java | 25 +- .../geometry/OperatorCentroid2DLocal.java | 267 +++++++++--------- .../esri/core/geometry/TestOGCCentroid.java | 97 +++---- 3 files changed, 187 insertions(+), 202 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java index f44a21f8..9453053d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java @@ -23,18 +23,15 @@ */ package com.esri.core.geometry; -public abstract class OperatorCentroid2D extends Operator -{ - @Override - public Type getType() - { - return Type.Centroid2D; - } - - public abstract Point2D execute(Geometry geometry, ProgressTracker progressTracker); - - public static OperatorCentroid2D local() - { - return (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Type.Centroid2D); - } +public abstract class OperatorCentroid2D extends Operator { + @Override + public Type getType() { + return Type.Centroid2D; + } + + public abstract Point2D execute(Geometry geometry, ProgressTracker progressTracker); + + public static OperatorCentroid2D local() { + return (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Type.Centroid2D); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java index 91b7c948..6a6a7394 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java @@ -25,140 +25,135 @@ import static java.lang.Math.sqrt; -public class OperatorCentroid2DLocal extends OperatorCentroid2D -{ - @Override - public Point2D execute(Geometry geometry, ProgressTracker progressTracker) - { - if (geometry.isEmpty()) { - return null; - } - - Geometry.Type geometryType = geometry.getType(); - switch (geometryType) { - case Point: - return ((Point) geometry).getXY(); - case Line: - return computeLineCentroid((Line) geometry); - case Envelope: - return ((Envelope) geometry).getCenterXY(); - case MultiPoint: - return computePointsCentroid((MultiPoint) geometry); - case Polyline: - return computePolylineCentroid(((Polyline) geometry)); - case Polygon: - return computePolygonCentroid((Polygon) geometry); - default: - throw new UnsupportedOperationException("Unexpected geometry type: " + geometryType); - } - } - - private static Point2D computeLineCentroid(Line line) - { - return new Point2D((line.getEndX() - line.getStartX()) / 2, (line.getEndY() - line.getStartY()) / 2); - } - - // Points centroid is arithmetic mean of the input points - private static Point2D computePointsCentroid(MultiPoint multiPoint) - { - double xSum = 0; - double ySum = 0; - int pointCount = multiPoint.getPointCount(); - Point2D point2D = new Point2D(); - for (int i = 0; i < pointCount; i++) { - multiPoint.getXY(i, point2D); - xSum += point2D.x; - ySum += point2D.y; - } - return new Point2D(xSum / pointCount, ySum / pointCount); - } - - // Lines centroid is weighted mean of each line segment, weight in terms of line length - private static Point2D computePolylineCentroid(Polyline polyline) - { - double xSum = 0; - double ySum = 0; - double weightSum = 0; - - Point2D startPoint = new Point2D(); - Point2D endPoint = new Point2D(); - for (int i = 0; i < polyline.getPathCount(); i++) { - polyline.getXY(polyline.getPathStart(i), startPoint); - polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); - double dx = endPoint.x - startPoint.x; - double dy = endPoint.y - startPoint.y; - double length = sqrt(dx * dx + dy * dy); - weightSum += length; - xSum += (startPoint.x + endPoint.x) * length / 2; - ySum += (startPoint.y + endPoint.y) * length / 2; - } - return new Point2D(xSum / weightSum, ySum / weightSum); - } - - // Polygon centroid: area weighted average of centroids in case of holes - private static Point2D computePolygonCentroid(Polygon polygon) - { - int pathCount = polygon.getPathCount(); - - if (pathCount == 1) { - return getPolygonSansHolesCentroid(polygon); - } - - double xSum = 0; - double ySum = 0; - double areaSum = 0; - - for (int i = 0; i < pathCount; i++) { - int startIndex = polygon.getPathStart(i); - int endIndex = polygon.getPathEnd(i); - - Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); - - Point2D centroid = getPolygonSansHolesCentroid(sansHoles); - double area = sansHoles.calculateArea2D(); - - xSum += centroid.x * area; - ySum += centroid.y * area; - areaSum += area; - } - - return new Point2D(xSum / areaSum, ySum / areaSum); - } - - private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) - { - Polyline boundary = new Polyline(); - boundary.startPath(polygon.getPoint(startIndex)); - for (int i = startIndex + 1; i < endIndex; i++) { - Point current = polygon.getPoint(i); - boundary.lineTo(current); - } - - final Polygon newPolygon = new Polygon(); - newPolygon.add(boundary, false); - return newPolygon; - } - - // Polygon sans holes centroid: - // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) - // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) - private static Point2D getPolygonSansHolesCentroid(Polygon polygon) - { - int pointCount = polygon.getPointCount(); - double xSum = 0; - double ySum = 0; - double signedArea = 0; - - Point2D current = new Point2D(); - Point2D next = new Point2D(); - for (int i = 0; i < pointCount; i++) { - polygon.getXY(i, current); - polygon.getXY((i + 1) % pointCount, next); - double ladder = current.x * next.y - next.x * current.y; - xSum += (current.x + next.x) * ladder; - ySum += (current.y + next.y) * ladder; - signedArea += ladder / 2; - } - return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); - } +public class OperatorCentroid2DLocal extends OperatorCentroid2D { + @Override + public Point2D execute(Geometry geometry, ProgressTracker progressTracker) { + if (geometry.isEmpty()) { + return null; + } + + Geometry.Type geometryType = geometry.getType(); + switch (geometryType) { + case Point: + return ((Point) geometry).getXY(); + case Line: + return computeLineCentroid((Line) geometry); + case Envelope: + return ((Envelope) geometry).getCenterXY(); + case MultiPoint: + return computePointsCentroid((MultiPoint) geometry); + case Polyline: + return computePolylineCentroid(((Polyline) geometry)); + case Polygon: + return computePolygonCentroid((Polygon) geometry); + default: + throw new UnsupportedOperationException("Unexpected geometry type: " + geometryType); + } + } + + private static Point2D computeLineCentroid(Line line) { + return new Point2D((line.getEndX() - line.getStartX()) / 2, (line.getEndY() - line.getStartY()) / 2); + } + + // Points centroid is arithmetic mean of the input points + private static Point2D computePointsCentroid(MultiPoint multiPoint) { + double xSum = 0; + double ySum = 0; + int pointCount = multiPoint.getPointCount(); + Point2D point2D = new Point2D(); + for (int i = 0; i < pointCount; i++) { + multiPoint.getXY(i, point2D); + xSum += point2D.x; + ySum += point2D.y; + } + return new Point2D(xSum / pointCount, ySum / pointCount); + } + + // Lines centroid is weighted mean of each line segment, weight in terms of line + // length + private static Point2D computePolylineCentroid(Polyline polyline) { + double xSum = 0; + double ySum = 0; + double weightSum = 0; + + Point2D startPoint = new Point2D(); + Point2D endPoint = new Point2D(); + for (int i = 0; i < polyline.getPathCount(); i++) { + polyline.getXY(polyline.getPathStart(i), startPoint); + polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); + double dx = endPoint.x - startPoint.x; + double dy = endPoint.y - startPoint.y; + double length = sqrt(dx * dx + dy * dy); + weightSum += length; + xSum += (startPoint.x + endPoint.x) * length / 2; + ySum += (startPoint.y + endPoint.y) * length / 2; + } + return new Point2D(xSum / weightSum, ySum / weightSum); + } + + // Polygon centroid: area weighted average of centroids in case of holes + private static Point2D computePolygonCentroid(Polygon polygon) { + int pathCount = polygon.getPathCount(); + + if (pathCount == 1) { + return getPolygonSansHolesCentroid(polygon); + } + + double xSum = 0; + double ySum = 0; + double areaSum = 0; + + for (int i = 0; i < pathCount; i++) { + int startIndex = polygon.getPathStart(i); + int endIndex = polygon.getPathEnd(i); + + Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); + + Point2D centroid = getPolygonSansHolesCentroid(sansHoles); + double area = sansHoles.calculateArea2D(); + + xSum += centroid.x * area; + ySum += centroid.y * area; + areaSum += area; + } + + return new Point2D(xSum / areaSum, ySum / areaSum); + } + + private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) { + Polyline boundary = new Polyline(); + boundary.startPath(polygon.getPoint(startIndex)); + for (int i = startIndex + 1; i < endIndex; i++) { + Point current = polygon.getPoint(i); + boundary.lineTo(current); + } + + final Polygon newPolygon = new Polygon(); + newPolygon.add(boundary, false); + return newPolygon; + } + + // Polygon sans holes centroid: + // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = + // 0 to N - 1) / (6 * signedArea) + // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = + // 0 to N - 1) / (6 * signedArea) + private static Point2D getPolygonSansHolesCentroid(Polygon polygon) { + int pointCount = polygon.getPointCount(); + double xSum = 0; + double ySum = 0; + double signedArea = 0; + + Point2D current = new Point2D(); + Point2D next = new Point2D(); + for (int i = 0; i < pointCount; i++) { + polygon.getXY(i, current); + polygon.getXY((i + 1) % pointCount, next); + double ladder = current.x * next.y - next.x * current.y; + xSum += (current.x + next.x) * ladder; + ySum += (current.y + next.y) * ladder; + signedArea += ladder / 2; + } + return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java index bf183bb9..d9d39adb 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java +++ b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java @@ -28,63 +28,56 @@ import org.junit.Assert; import org.junit.Test; -public class TestOGCCentroid -{ - @Test - public void testPoint() - { - assertCentroid("POINT (1 2)", new Point(1, 2)); - assertEmptyCentroid("POINT EMPTY"); - } +public class TestOGCCentroid { + @Test + public void testPoint() { + assertCentroid("POINT (1 2)", new Point(1, 2)); + assertEmptyCentroid("POINT EMPTY"); + } - @Test - public void testLineString() - { - assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); - assertEmptyCentroid("LINESTRING EMPTY"); - } + @Test + public void testLineString() { + assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); + assertEmptyCentroid("LINESTRING EMPTY"); + } - @Test - public void testPolygon() - { - assertCentroid("POLYGON ((1 1, 1 4, 4 4, 4 1))'", new Point(2.5, 2.5)); - assertCentroid("POLYGON ((1 1, 5 1, 3 4))", new Point(3, 2)); - assertCentroid("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))", new Point(2.5416666666666665, 2.5416666666666665)); - assertEmptyCentroid("POLYGON EMPTY"); - } + @Test + public void testPolygon() { + assertCentroid("POLYGON ((1 1, 1 4, 4 4, 4 1))'", new Point(2.5, 2.5)); + assertCentroid("POLYGON ((1 1, 5 1, 3 4))", new Point(3, 2)); + assertCentroid("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))", + new Point(2.5416666666666665, 2.5416666666666665)); + assertEmptyCentroid("POLYGON EMPTY"); + } - @Test - public void testMultiPoint() - { - assertCentroid("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", new Point(2.5, 5)); - assertEmptyCentroid("MULTIPOINT EMPTY"); - } + @Test + public void testMultiPoint() { + assertCentroid("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", new Point(2.5, 5)); + assertEmptyCentroid("MULTIPOINT EMPTY"); + } - @Test - public void testMultiLineString() - { - assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); - assertEmptyCentroid("MULTILINESTRING EMPTY"); - } + @Test + public void testMultiLineString() { + assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); + assertEmptyCentroid("MULTILINESTRING EMPTY"); + } - @Test - public void testMultiPolygon() - { - assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", new Point (3.3333333333333335,4)); - assertEmptyCentroid("MULTIPOLYGON EMPTY"); - } + @Test + public void testMultiPolygon() { + assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", + new Point(3.3333333333333335, 4)); + assertEmptyCentroid("MULTIPOLYGON EMPTY"); + } - private static void assertCentroid(String wkt, Point expectedCentroid) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - OGCGeometry centroid = geometry.centroid(); - Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); - } + private static void assertCentroid(String wkt, Point expectedCentroid) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); + } - private static void assertEmptyCentroid(String wkt) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - OGCGeometry centroid = geometry.centroid(); - Assert.assertEquals(centroid, new OGCPoint(new Point(), geometry.getEsriSpatialReference())); - } + private static void assertEmptyCentroid(String wkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(new Point(), geometry.getEsriSpatialReference())); + } } From 8e390b90639bf690550012fe7f4b412f7f7795bd Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sat, 3 Aug 2019 04:18:19 -0700 Subject: [PATCH 090/116] centroid fixes (#227) --- .gitignore | 2 + .travis.yml | 9 +- pom.xml | 6 +- .../geometry/OperatorCentroid2DLocal.java | 144 +++++------ .../com/esri/core/geometry/TestCentroid.java | 226 +++++++++++------- .../esri/core/geometry/TestOGCCentroid.java | 25 +- 6 files changed, 238 insertions(+), 174 deletions(-) diff --git a/.gitignore b/.gitignore index 4f5d1e04..f2a3f602 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ Thumbs.db target/* /bin/ /target/ + +.metadata/ diff --git a/.travis.yml b/.travis.yml index d1fa4fad..2113e3d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: java jdk: - - openjdk7 - - openjdk8 - - oraclejdk8 - - oraclejdk9 + - openjdk9 + - openjdk10 + - openjdk14 +# - oraclejdk9 + - oraclejdk11 notifications: email: false diff --git a/pom.xml b/pom.xml index eec05faa..09ac3899 100755 --- a/pom.xml +++ b/pom.xml @@ -94,8 +94,8 @@ UTF-8 - 1.6 - 1.6 + 1.7 + 1.7 2.9.6 @@ -194,7 +194,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.2 + 1.6.8 true ossrh diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java index 6a6a7394..ce7079b8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2019 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,8 +23,6 @@ */ package com.esri.core.geometry; -import static java.lang.Math.sqrt; - public class OperatorCentroid2DLocal extends OperatorCentroid2D { @Override public Point2D execute(Geometry geometry, ProgressTracker progressTracker) { @@ -56,7 +54,7 @@ private static Point2D computeLineCentroid(Line line) { } // Points centroid is arithmetic mean of the input points - private static Point2D computePointsCentroid(MultiPoint multiPoint) { + private static Point2D computePointsCentroid(MultiVertexGeometry multiPoint) { double xSum = 0; double ySum = 0; int pointCount = multiPoint.getPointCount(); @@ -71,89 +69,75 @@ private static Point2D computePointsCentroid(MultiPoint multiPoint) { // Lines centroid is weighted mean of each line segment, weight in terms of line // length - private static Point2D computePolylineCentroid(Polyline polyline) { - double xSum = 0; - double ySum = 0; - double weightSum = 0; - - Point2D startPoint = new Point2D(); - Point2D endPoint = new Point2D(); - for (int i = 0; i < polyline.getPathCount(); i++) { - polyline.getXY(polyline.getPathStart(i), startPoint); - polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); - double dx = endPoint.x - startPoint.x; - double dy = endPoint.y - startPoint.y; - double length = sqrt(dx * dx + dy * dy); - weightSum += length; - xSum += (startPoint.x + endPoint.x) * length / 2; - ySum += (startPoint.y + endPoint.y) * length / 2; - } - return new Point2D(xSum / weightSum, ySum / weightSum); - } - - // Polygon centroid: area weighted average of centroids in case of holes - private static Point2D computePolygonCentroid(Polygon polygon) { - int pathCount = polygon.getPathCount(); - - if (pathCount == 1) { - return getPolygonSansHolesCentroid(polygon); + private static Point2D computePolylineCentroid(MultiPath polyline) { + double totalLength = polyline.calculateLength2D(); + if (totalLength == 0) { + return computePointsCentroid(polyline); } - - double xSum = 0; - double ySum = 0; - double areaSum = 0; - - for (int i = 0; i < pathCount; i++) { - int startIndex = polygon.getPathStart(i); - int endIndex = polygon.getPathEnd(i); - - Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); - - Point2D centroid = getPolygonSansHolesCentroid(sansHoles); - double area = sansHoles.calculateArea2D(); - - xSum += centroid.x * area; - ySum += centroid.y * area; - areaSum += area; + + MathUtils.KahanSummator xSum = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator ySum = new MathUtils.KahanSummator(0); + Point2D point = new Point2D(); + SegmentIterator iter = polyline.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + seg.getCoord2D(0.5, point); + double length = seg.calculateLength2D(); + point.scale(length); + xSum.add(point.x); + ySum.add(point.y); + } } - - return new Point2D(xSum / areaSum, ySum / areaSum); + + return new Point2D(xSum.getResult() / totalLength, ySum.getResult() / totalLength); } - private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) { - Polyline boundary = new Polyline(); - boundary.startPath(polygon.getPoint(startIndex)); - for (int i = startIndex + 1; i < endIndex; i++) { - Point current = polygon.getPoint(i); - boundary.lineTo(current); + // Polygon centroid: area weighted average of centroids + private static Point2D computePolygonCentroid(Polygon polygon) { + double totalArea = polygon.calculateArea2D(); + if (totalArea == 0) + { + return computePolylineCentroid(polygon); } - - final Polygon newPolygon = new Polygon(); - newPolygon.add(boundary, false); - return newPolygon; - } - - // Polygon sans holes centroid: - // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = - // 0 to N - 1) / (6 * signedArea) - // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = - // 0 to N - 1) / (6 * signedArea) - private static Point2D getPolygonSansHolesCentroid(Polygon polygon) { - int pointCount = polygon.getPointCount(); - double xSum = 0; - double ySum = 0; - double signedArea = 0; - + + MathUtils.KahanSummator xSum = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator ySum = new MathUtils.KahanSummator(0); + Point2D startPoint = new Point2D(); Point2D current = new Point2D(); Point2D next = new Point2D(); - for (int i = 0; i < pointCount; i++) { - polygon.getXY(i, current); - polygon.getXY((i + 1) % pointCount, next); - double ladder = current.x * next.y - next.x * current.y; - xSum += (current.x + next.x) * ladder; - ySum += (current.y + next.y) * ladder; - signedArea += ladder / 2; + Point2D origin = polygon.getXY(0); + + for (int ipath = 0, npaths = polygon.getPathCount(); ipath < npaths; ipath++) { + int startIndex = polygon.getPathStart(ipath); + int endIndex = polygon.getPathEnd(ipath); + int pointCount = endIndex - startIndex; + if (pointCount < 3) { + continue; + } + + polygon.getXY(startIndex, startPoint); + polygon.getXY(startIndex + 1, current); + current.sub(startPoint); + for (int i = startIndex + 2, n = endIndex; i < n; i++) { + polygon.getXY(i, next); + next.sub(startPoint); + double twiceTriangleArea = next.x * current.y - current.x * next.y; + xSum.add((current.x + next.x) * twiceTriangleArea); + ySum.add((current.y + next.y) * twiceTriangleArea); + current.setCoords(next); + } + + startPoint.sub(origin); + startPoint.scale(6.0 * polygon.calculateRingArea2D(ipath)); + //add weighted startPoint + xSum.add(startPoint.x); + ySum.add(startPoint.y); } - return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); + + totalArea *= 6.0; + Point2D res = new Point2D(xSum.getResult() / totalArea, ySum.getResult() / totalArea); + res.add(origin); + return res; } } diff --git a/src/test/java/com/esri/core/geometry/TestCentroid.java b/src/test/java/com/esri/core/geometry/TestCentroid.java index 58c430fb..3065ec9e 100644 --- a/src/test/java/com/esri/core/geometry/TestCentroid.java +++ b/src/test/java/com/esri/core/geometry/TestCentroid.java @@ -26,90 +26,144 @@ import org.junit.Assert; import org.junit.Test; -public class TestCentroid -{ - @Test - public void testPoint() - { - assertCentroid(new Point(1, 2), new Point2D(1, 2)); - } - - @Test - public void testLine() - { - assertCentroid(new Line(0, 0, 10, 20), new Point2D(5, 10)); - } - - @Test - public void testEnvelope() - { - assertCentroid(new Envelope(1, 2, 3,4), new Point2D(2, 3)); - assertCentroid(new Envelope(), null); - } - - @Test - public void testMultiPoint() - { - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0, 0); - multiPoint.add(1, 2); - multiPoint.add(3, 1); - multiPoint.add(0, 1); - - assertCentroid(multiPoint, new Point2D(1, 1)); - assertCentroid(new MultiPoint(), null); - } - - @Test - public void testPolyline() - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(1, 2); - polyline.lineTo(3, 4); - assertCentroid(polyline, new Point2D(1.5, 2)); - - polyline.startPath(1, -1); - polyline.lineTo(2, 0); - polyline.lineTo(10, 1); - assertCentroid(polyline, new Point2D(4.093485180902371 , 0.7032574095488145)); - - assertCentroid(new Polyline(), null); - } - - @Test - public void testPolygon() - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(1, 2); - polygon.lineTo(3, 4); - polygon.lineTo(5, 2); - polygon.lineTo(0, 0); - assertCentroid(polygon, new Point2D(2.5, 2)); - - // add a hole - polygon.startPath(2, 2); - polygon.lineTo(2.3, 2); - polygon.lineTo(2.3, 2.4); - polygon.lineTo(2, 2); - assertCentroid(polygon, new Point2D(2.5022670025188916 , 1.9989924433249369)); - - // add another polygon - polygon.startPath(-1, -1); - polygon.lineTo(3, -1); - polygon.lineTo(0.5, -2); - polygon.lineTo(-1, -1); - assertCentroid(polygon, new Point2D(2.166465459423206 , 1.3285043594902748)); - - assertCentroid(new Polygon(), null); - } - - private static void assertCentroid(Geometry geometry, Point2D expectedCentroid) - { - OperatorCentroid2D operator = (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Centroid2D); - - Point2D actualCentroid = operator.execute(geometry, null); - Assert.assertEquals(expectedCentroid, actualCentroid); - } +public class TestCentroid { + @Test + public void testPoint() { + assertCentroid(new Point(1, 2), new Point2D(1, 2)); + } + + @Test + public void testLine() { + assertCentroid(new Line(0, 0, 10, 20), new Point2D(5, 10)); + } + + @Test + public void testEnvelope() { + assertCentroid(new Envelope(1, 2, 3, 4), new Point2D(2, 3)); + assertCentroidEmpty(new Envelope()); + } + + @Test + public void testMultiPoint() { + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(1, 2); + multiPoint.add(3, 1); + multiPoint.add(0, 1); + + assertCentroid(multiPoint, new Point2D(1, 1)); + assertCentroidEmpty(new MultiPoint()); + } + + @Test + public void testPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 2); + polyline.lineTo(3, 4); + assertCentroid(polyline, new Point2D(1.3377223398316207, 2.1169631197754946)); + + polyline.startPath(1, -1); + polyline.lineTo(2, 0); + polyline.lineTo(10, 1); + assertCentroid(polyline, new Point2D(3.93851092460519, 0.9659173294165462)); + + assertCentroidEmpty(new Polyline()); + } + + @Test + public void testPolygon() { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(1, 2); + polygon.lineTo(3, 4); + polygon.lineTo(5, 2); + polygon.lineTo(0, 0); + assertCentroid(polygon, new Point2D(2.5, 2)); + + // add a hole + polygon.startPath(2, 2); + polygon.lineTo(2.3, 2); + polygon.lineTo(2.3, 2.4); + polygon.lineTo(2, 2); + assertCentroid(polygon, new Point2D(2.5022670025188916, 1.9989924433249369)); + + // add another polygon + polygon.startPath(-1, -1); + polygon.lineTo(3, -1); + polygon.lineTo(0.5, -2); + polygon.lineTo(-1, -1); + assertCentroid(polygon, new Point2D(2.166465459423206, 1.3285043594902748)); + + assertCentroidEmpty(new Polygon()); + } + + @Test + public void testSmallPolygon() { + // https://github.com/Esri/geometry-api-java/issues/225 + + Polygon polygon = new Polygon(); + polygon.startPath(153.492818, -28.13729); + polygon.lineTo(153.492821, -28.137291); + polygon.lineTo(153.492816, -28.137289); + polygon.lineTo(153.492818, -28.13729); + + assertCentroid(polygon, new Point2D(153.492818333333333, -28.13729)); + } + + @Test + public void testZeroAreaPolygon() { + Polygon polygon = new Polygon(); + polygon.startPath(153, 28); + polygon.lineTo(163, 28); + polygon.lineTo(153, 28); + + Polyline polyline = (Polyline) polygon.getBoundary(); + Point2D expectedCentroid = new Point2D(158, 28); + + assertCentroid(polyline, expectedCentroid); + assertCentroid(polygon, expectedCentroid); + } + + @Test + public void testDegeneratesToPointPolygon() { + Polygon polygon = new Polygon(); + polygon.startPath(-8406364, 560828); + polygon.lineTo(-8406364, 560828); + polygon.lineTo(-8406364, 560828); + polygon.lineTo(-8406364, 560828); + + assertCentroid(polygon, new Point2D(-8406364, 560828)); + } + + @Test + public void testZeroLengthPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(153, 28); + polyline.lineTo(153, 28); + + assertCentroid(polyline, new Point2D(153, 28)); + } + + @Test + public void testDegeneratesToPointPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(-8406364, 560828); + polyline.lineTo(-8406364, 560828); + + assertCentroid(polyline, new Point2D(-8406364, 560828)); + } + + private static void assertCentroid(Geometry geometry, Point2D expectedCentroid) { + + Point2D actualCentroid = OperatorCentroid2D.local().execute(geometry, null); + Assert.assertEquals(expectedCentroid.x, actualCentroid.x, 1e-13); + Assert.assertEquals(expectedCentroid.y, actualCentroid.y, 1e-13); + } + + private static void assertCentroidEmpty(Geometry geometry) { + + Point2D actualCentroid = OperatorCentroid2D.local().execute(geometry, null); + Assert.assertTrue(actualCentroid == null); + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java index d9d39adb..a64c67b5 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java +++ b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java @@ -38,6 +38,10 @@ public void testPoint() { @Test public void testLineString() { assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); + //closed path + assertCentroid("LINESTRING (0 0, 1 0, 1 1, 0 1, 0 0)", new Point(0.5, 0.5)); + //all points coincide + assertCentroid("LINESTRING (0 0, 0 0, 0 0, 0 0, 0 0)", new Point(0.0, 0.0)); assertEmptyCentroid("LINESTRING EMPTY"); } @@ -59,20 +63,39 @@ public void testMultiPoint() { @Test public void testMultiLineString() { assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); + assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 3 3, 4 4))')))", new Point(3, 2.0355339059327378)); + assertCentroid("MULTILINESTRING ((0 0, 0 0, 0 0), (1 1, 1 1, 1 1, 1 1))", new Point(0.571428571428571429, 0.571428571428571429)); assertEmptyCentroid("MULTILINESTRING EMPTY"); } @Test public void testMultiPolygon() { + assertCentroid("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))", new Point(1.5, 1.5)); + assertCentroid("MULTIPOLYGON (((2 2, 3 2, 3 3, 2 3, 2 2)), ((4 4, 5 4, 5 5, 4 5, 4 4)))", new Point(3.5, 3.5)); assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", new Point(3.3333333333333335, 4)); + + //hole is same as exterior - compute as polyline + assertCentroid("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0), (0 0, 0 1, 1 1, 1 0, 0 0)))", new Point(0.5, 0.5)); + + //polygon is only vertices - compute as multipoint. Note that the closing vertex of the ring is not counted + assertCentroid("MULTIPOLYGON (((0 0, 0 0, 0 0), (1 1, 1 1, 1 1, 1 1)))", new Point(0.6, 0.6)); + + // test cases from https://github.com/Esri/geometry-api-java/issues/225 + assertCentroid( + "MULTIPOLYGON (((153.492818 -28.13729, 153.492821 -28.137291, 153.492816 -28.137289, 153.492818 -28.13729)))", + new Point(153.49281833333333, -28.13729)); + assertCentroid( + "MULTIPOLYGON (((153.112475 -28.360526, 153.1124759 -28.360527, 153.1124759 -28.360526, 153.112475 -28.360526)))", + new Point(153.1124756, -28.360526333333333)); assertEmptyCentroid("MULTIPOLYGON EMPTY"); } private static void assertCentroid(String wkt, Point expectedCentroid) { OGCGeometry geometry = OGCGeometry.fromText(wkt); OGCGeometry centroid = geometry.centroid(); - Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); + Assert.assertEquals(((OGCPoint)centroid).X(), expectedCentroid.getX(), 1e-13); + Assert.assertEquals(((OGCPoint)centroid).Y(), expectedCentroid.getY(), 1e-13); } private static void assertEmptyCentroid(String wkt) { From 43ece8840f291f6eab8bfd44bbca5480a18d3eae Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 6 Aug 2019 11:23:36 -0700 Subject: [PATCH 091/116] Remove duplicate interface (#237) --- .../com/esri/core/geometry/DirtyFlags.java | 64 --------------- .../com/esri/core/geometry/EditShape.java | 4 +- .../geometry/MultiVertexGeometryImpl.java | 77 +++++++------------ 3 files changed, 28 insertions(+), 117 deletions(-) delete mode 100644 src/main/java/com/esri/core/geometry/DirtyFlags.java diff --git a/src/main/java/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java deleted file mode 100644 index 36ccafda..00000000 --- a/src/main/java/com/esri/core/geometry/DirtyFlags.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -interface DirtyFlags { - public static final int dirtyIsKnownSimple = 1; // !<0 when is_weak_simple - // or is_strong_simple flag - // is valid - public static final int isWeakSimple = 2; // ! Date: Tue, 6 Aug 2019 11:24:03 -0700 Subject: [PATCH 092/116] Cleanup unused variables and code and small optimization (#233) --- .../com/esri/core/geometry/Simplificator.java | 410 +++++++----------- 1 file changed, 154 insertions(+), 256 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index e0401cfa..a5102528 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -33,16 +33,16 @@ class Simplificator { private AttributeStreamOfInt32 m_bunchEdgeIndices; // private AttributeStreamOfInt32 m_orphanVertices; - private int m_dbgCounter; private int m_sortedVerticesListIndex; private int m_userIndexSortedIndexToVertex; private int m_userIndexSortedAngleIndexToVertex; private int m_nextVertexToProcess; private int m_firstCoincidentVertex; - private int m_knownSimpleResult; - private boolean m_bWinding; + //private int m_knownSimpleResult; private boolean m_fixSelfTangency; private ProgressTracker m_progressTracker; + private int[] m_ar = null; + private int[] m_br = null; private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, @@ -91,9 +91,15 @@ private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { } } - static class SimplificatorAngleComparer extends + static private class SimplificatorAngleComparer extends AttributeStreamOfInt32.IntComparator { - Simplificator m_parent; + private Simplificator m_parent; + private Point2D pt1 = new Point2D(); + private Point2D pt2 = new Point2D(); + private Point2D pt10 = new Point2D(); + private Point2D pt20 = new Point2D(); + private Point2D v1 = new Point2D(); + private Point2D v2 = new Point2D(); public SimplificatorAngleComparer(Simplificator parent) { m_parent = parent; @@ -101,19 +107,35 @@ public SimplificatorAngleComparer(Simplificator parent) { @Override public int compare(int v1, int v2) { - return m_parent._compareAngles(v1, v2); + return _compareAngles(v1, v2); } + private int _compareAngles(int index1, int index2) { + int vert1 = m_parent.m_bunchEdgeEndPoints.get(index1); + m_parent.m_shape.getXY(vert1, pt1); + int vert2 = m_parent.m_bunchEdgeEndPoints.get(index2); + m_parent.m_shape.getXY(vert2, pt2); + + if (pt1.isEqual(pt2)) + return 0;// overlap case + + int vert10 = m_parent.m_bunchEdgeCenterPoints.get(index1); + m_parent.m_shape.getXY(vert10, pt10); + + int vert20 = m_parent.m_bunchEdgeCenterPoints.get(index2); + m_parent.m_shape.getXY(vert20, pt20); + + v1.sub(pt1, pt10); + v2.sub(pt2, pt20); + int result = Point2D._compareVectors(v1, v2); + return result; + } } private boolean _processBunch() { boolean bModified = false; - int iter = 0; Point2D ptCenter = new Point2D(); while (true) { - m_dbgCounter++;// only for debugging - iter++; - // _ASSERT(iter < 10); if (m_bunchEdgeEndPoints == null) { m_bunchEdgeEndPoints = new AttributeStreamOfInt32(0); m_bunchEdgeCenterPoints = new AttributeStreamOfInt32(0); @@ -129,25 +151,17 @@ private boolean _processBunch() { boolean bFirst = true; while (currentVertex != m_nextVertexToProcess) { int v = m_sortedVertices.getData(currentVertex); - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(v, pt); - double y = pt.x; - } if (bFirst) { m_shape.getXY(v, ptCenter); bFirst = false; } int vertP = m_shape.getPrevVertex(v); int vertN = m_shape.getNextVertex(v); - // _ASSERT(vertP != vertN || m_shape.getPrevVertex(vertN) == v - // && m_shape.getNextVertex(vertP) == v); int id = m_shape.getUserIndex(vertP, m_userIndexSortedAngleIndexToVertex); if (id != 0xdeadbeef)// avoid adding a point twice { - // _ASSERT(id == -1); m_bunchEdgeEndPoints.add(vertP); m_shape.setUserIndex(vertP, m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark @@ -165,7 +179,6 @@ private boolean _processBunch() { m_userIndexSortedAngleIndexToVertex); if (id2 != 0xdeadbeef) // avoid adding a point twice { - // _ASSERT(id2 == -1); m_bunchEdgeEndPoints.add(vertN); m_shape.setUserIndex(vertN, m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark @@ -189,8 +202,6 @@ private boolean _processBunch() { // the edge, connecting the endpoint with the bunch center) m_bunchEdgeIndices.Sort(0, m_bunchEdgeIndices.size(), new SimplificatorAngleComparer(this)); - // SORTDYNAMICARRAYEX(m_bunchEdgeIndices, int, 0, - // m_bunchEdgeIndices.size(), SimplificatorAngleComparer, this); for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { int indexL = m_bunchEdgeIndices.get(i); @@ -199,11 +210,6 @@ private boolean _processBunch() { m_userIndexSortedAngleIndexToVertex, i);// rember the // sort by angle // order - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - double y = pt.x; - } } boolean bCrossOverResolved = _processCrossOvers(ptCenter);// see of @@ -254,7 +260,6 @@ private boolean _processCrossOvers(Point2D ptCenter) { int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); - // _ASSERT(vertexB2 != vertexB1); int vertexA1 = m_shape.getNextVertex(vertexB1); if (!m_shape.isEqualXY(vertexA1, ptCenter)) @@ -263,9 +268,6 @@ private boolean _processCrossOvers(Point2D ptCenter) { if (!m_shape.isEqualXY(vertexA2, ptCenter)) vertexA2 = m_shape.getPrevVertex(vertexB2); - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); - boolean bDirection1 = _getDirection(vertexA1, vertexB1); boolean bDirection2 = _getDirection(vertexA2, vertexB2); int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) @@ -331,9 +333,6 @@ else if (_removeSpike(vertexC2)) if (!m_shape.isEqualXY(vertexA2, ptCenter)) vertexA2 = m_shape.getPrevVertex(vertexB2); - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); - boolean bDirection1 = _getDirection(vertexA1, vertexB1); boolean bDirection2 = _getDirection(vertexA2, vertexB2); int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) @@ -355,9 +354,11 @@ else if (_removeSpike(vertexC2)) return bFound; } - static class SimplificatorVertexComparer extends + static private class SimplificatorVertexComparer extends AttributeStreamOfInt32.IntComparator { - Simplificator m_parent; + private Simplificator m_parent; + private Point2D pt1 = new Point2D(); + private Point2D pt2 = new Point2D(); SimplificatorVertexComparer(Simplificator parent) { m_parent = parent; @@ -365,9 +366,21 @@ static class SimplificatorVertexComparer extends @Override public int compare(int v1, int v2) { - return m_parent._compareVerticesSimple(v1, v2); + return _compareVerticesSimple(v1, v2); } + private int _compareVerticesSimple(int v1, int v2) { + m_parent.m_shape.getXY(v1, pt1); + m_parent.m_shape.getXY(v2, pt2); + int res = pt1.compare(pt2); + if (res == 0) {// sort equal vertices by the path ID + int i1 = m_parent.m_shape.getPathFromVertex(v1); + int i2 = m_parent.m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); + } + + return res; + } } private boolean _simplify() { @@ -381,8 +394,6 @@ private boolean _simplify() { assert (m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleOddEven); } boolean bChanged = false; - boolean bNeedWindingRepeat = true; - boolean bWinding = false; m_userIndexSortedIndexToVertex = -1; m_userIndexSortedAngleIndexToVertex = -1; @@ -406,8 +417,6 @@ private boolean _simplify() { // Sort verticesSorter.Sort(0, pointCount, new SimplificatorVertexComparer(this)); - // SORTDYNAMICARRAYEX(verticesSorter, int, 0, pointCount, - // SimplificatorVertexComparer, this); // Copy sorted vertices to the m_sortedVertices list. Make a mapping // from the edit shape vertices to the sorted vertices. @@ -424,11 +433,6 @@ private boolean _simplify() { m_sortedVerticesListIndex = m_sortedVertices.createList(0); for (int i = 0; i < pointCount; i++) { int vertex = verticesSorter.get(i); - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt);// for debugging - double y = pt.x; - } int vertexlistIndex = m_sortedVertices.addElement( m_sortedVerticesListIndex, vertex); m_shape.setUserIndex(vertex, m_userIndexSortedIndexToVertex, @@ -452,120 +456,100 @@ private boolean _simplify() { if (_cleanupSpikes())// cleanup any spikes on the polygon. bChanged = true; - // External iteration loop for the simplificator. - // ST. I am not sure if it actually needs this loop. TODO: figure this - // out. - while (bNeedWindingRepeat) { - bNeedWindingRepeat = false; + // Simplify polygon + int iRepeatNum = 0; + boolean bNeedRepeat = false; - int max_iter = m_shape.getPointCount(m_geometry) + 10 > 30 ? 1000 - : (m_shape.getPointCount(m_geometry) + 10) - * (m_shape.getPointCount(m_geometry) + 10); - - // Simplify polygon - int iRepeatNum = 0; - boolean bNeedRepeat = false; - - // Internal iteration loop for the simplificator. - // ST. I am not sure if it actually needs this loop. TODO: figure - // this out. - do// while (bNeedRepeat); - { - bNeedRepeat = false; - - boolean bVertexRecheck = false; - m_firstCoincidentVertex = -1; - int coincidentCount = 0; - Point2D ptFirst = new Point2D(); - Point2D pt = new Point2D(); - // Main loop of the simplificator. Go through the vertices and - // for those that have same coordinates, - for (int vlistindex = m_sortedVertices - .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList - .nullNode();) { - int vertex = m_sortedVertices.getData(vlistindex); - {// debug - // Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - double d = pt.x; - } - - if (m_firstCoincidentVertex != -1) { - // Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - if (ptFirst.isEqual(pt)) { - coincidentCount++; - } else { - ptFirst.setCoords(pt); - m_nextVertexToProcess = vlistindex;// we remeber the - // next index in - // the member - // variable to - // allow it to - // be updated if - // a vertex is - // removed - // inside of the - // _ProcessBunch. - if (coincidentCount > 0) { - boolean result = _processBunch();// process a - // bunch of - // coinciding - // vertices - if (result) {// something has changed. - // Note that ProcessBunch may - // change m_nextVertexToProcess - // and m_firstCoincidentVertex. - bNeedRepeat = true; - if (m_nextVertexToProcess != IndexMultiDCList - .nullNode()) { - int v = m_sortedVertices - .getData(m_nextVertexToProcess); - m_shape.getXY(v, ptFirst); - } + // Internal iteration loop for the simplificator. + // ST. I am not sure if it actually needs this loop. TODO: figure + // this out. + do// while (bNeedRepeat); + { + bNeedRepeat = false; + + m_firstCoincidentVertex = -1; + int coincidentCount = 0; + Point2D ptFirst = new Point2D(); + Point2D pt = new Point2D(); + // Main loop of the simplificator. Go through the vertices and + // for those that have same coordinates, + for (int vlistindex = m_sortedVertices + .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList + .nullNode();) { + int vertex = m_sortedVertices.getData(vlistindex); + + if (m_firstCoincidentVertex != -1) { + // Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + if (ptFirst.isEqual(pt)) { + coincidentCount++; + } else { + ptFirst.setCoords(pt); + m_nextVertexToProcess = vlistindex;// we remeber the + // next index in + // the member + // variable to + // allow it to + // be updated if + // a vertex is + // removed + // inside of the + // _ProcessBunch. + if (coincidentCount > 0) { + boolean result = _processBunch();// process a + // bunch of + // coinciding + // vertices + if (result) {// something has changed. + // Note that ProcessBunch may + // change m_nextVertexToProcess + // and m_firstCoincidentVertex. + bNeedRepeat = true; + if (m_nextVertexToProcess != IndexMultiDCList + .nullNode()) { + int v = m_sortedVertices + .getData(m_nextVertexToProcess); + m_shape.getXY(v, ptFirst); } } - - vlistindex = m_nextVertexToProcess; - m_firstCoincidentVertex = vlistindex; - coincidentCount = 0; } - } else { + + vlistindex = m_nextVertexToProcess; m_firstCoincidentVertex = vlistindex; - m_shape.getXY(m_sortedVertices.getData(vlistindex), - ptFirst); coincidentCount = 0; } - - if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above - vlistindex = m_sortedVertices.getNext(vlistindex); - } - - m_nextVertexToProcess = -1; - - if (coincidentCount > 0) { - boolean result = _processBunch(); - if (result) - bNeedRepeat = true; + } else { + m_firstCoincidentVertex = vlistindex; + m_shape.getXY(m_sortedVertices.getData(vlistindex), + ptFirst); + coincidentCount = 0; } - if (iRepeatNum++ > 10) { - throw GeometryException.GeometryInternalError(); - } + if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above + vlistindex = m_sortedVertices.getNext(vlistindex); + } - if (bNeedRepeat) - _fixOrphanVertices();// fix broken structure of the shape + m_nextVertexToProcess = -1; - if (_cleanupSpikes()) + if (coincidentCount > 0) { + boolean result = _processBunch(); + if (result) bNeedRepeat = true; + } + + if (iRepeatNum++ > 10) { + throw GeometryException.GeometryInternalError(); + } - bNeedWindingRepeat |= bNeedRepeat && bWinding; + if (bNeedRepeat) + _fixOrphanVertices();// fix broken structure of the shape - bChanged |= bNeedRepeat; + if (_cleanupSpikes()) + bNeedRepeat = true; - } while (bNeedRepeat); + bChanged |= bNeedRepeat; - }// while (bNeedWindingRepeat) + } while (bNeedRepeat); // Now process rings. Fix ring orientation and determine rings that need // to be deleted. @@ -581,11 +565,8 @@ private boolean _simplify() { private boolean _getDirection(int vert1, int vert2) { if (m_shape.getNextVertex(vert2) == vert1) { - // _ASSERT(m_shape.getPrevVertex(vert1) == vert2); return false; } else { - // _ASSERT(m_shape.getPrevVertex(vert2) == vert1); - // _ASSERT(m_shape.getNextVertex(vert1) == vert2); return true; } } @@ -593,8 +574,6 @@ private boolean _getDirection(int vert1, int vert2) { private boolean _detectAndResolveCrossOver(boolean bDirection1, boolean bDirection2, int vertexB1, int vertexA1, int vertexC1, int vertexB2, int vertexA2, int vertexC2) { - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexB2)); - // _ASSERT(!m_shape.isEqualXY(vertexC1, vertexC2)); if (vertexA1 == vertexA2) { _removeAngleSortInfo(vertexB1); @@ -602,17 +581,6 @@ private boolean _detectAndResolveCrossOver(boolean bDirection1, return false; } - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC2)); - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC2)); - // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexB1)); - // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexB2)); - // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexC2)); - - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // get indices of the vertices for the angle sort. int iB1 = m_shape.getUserIndex(vertexB1, m_userIndexSortedAngleIndexToVertex); @@ -622,44 +590,42 @@ private boolean _detectAndResolveCrossOver(boolean bDirection1, m_userIndexSortedAngleIndexToVertex); int iC2 = m_shape.getUserIndex(vertexC2, m_userIndexSortedAngleIndexToVertex); - // _ASSERT(iB1 >= 0); - // _ASSERT(iC1 >= 0); - // _ASSERT(iB2 >= 0); - // _ASSERT(iC2 >= 0); // Sort the indices to restore the angle-sort order - int[] ar = new int[8]; - int[] br = new int[4]; - - ar[0] = 0; - br[0] = iB1; - ar[1] = 0; - br[1] = iC1; - ar[2] = 1; - br[2] = iB2; - ar[3] = 1; - br[3] = iC2; + + if (m_ar == null) { + m_ar = new int[8]; + m_br = new int[4]; + } + m_ar[0] = 0; + m_br[0] = iB1; + m_ar[1] = 0; + m_br[1] = iC1; + m_ar[2] = 1; + m_br[2] = iB2; + m_ar[3] = 1; + m_br[3] = iC2; for (int j = 1; j < 4; j++)// insertion sort { - int key = br[j]; - int data = ar[j]; + int key = m_br[j]; + int data = m_ar[j]; int i = j - 1; - while (i >= 0 && br[i] > key) { - br[i + 1] = br[i]; - ar[i + 1] = ar[i]; + while (i >= 0 && m_br[i] > key) { + m_br[i + 1] = m_br[i]; + m_ar[i + 1] = m_ar[i]; i--; } - br[i + 1] = key; - ar[i + 1] = data; + m_br[i + 1] = key; + m_ar[i + 1] = data; } int detector = 0; - if (ar[0] != 0) + if (m_ar[0] != 0) detector |= 1; - if (ar[1] != 0) + if (m_ar[1] != 0) detector |= 2; - if (ar[2] != 0) + if (m_ar[2] != 0) detector |= 4; - if (ar[3] != 0) + if (m_ar[3] != 0) detector |= 8; if (detector != 5 && detector != 10)// not an overlap return false; @@ -701,19 +667,8 @@ private boolean _detectAndResolveCrossOver(boolean bDirection1, private void _resolveOverlap(boolean bDirection1, boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, int vertexB2) { - if (m_bWinding) { - _resolveOverlapWinding(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } else { - _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } - } - - private void _resolveOverlapWinding(boolean bDirection1, - boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, - int vertexB2) { - throw new GeometryException("not implemented."); + _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); } private void _resolveOverlapOddEven(boolean bDirection1, @@ -721,8 +676,6 @@ private void _resolveOverlapOddEven(boolean bDirection1, int vertexB2) { if (bDirection1 != bDirection2) { if (bDirection1) { - // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); - // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); m_shape.setNextVertex_(vertexA1, vertexA2); // B1< B2 m_shape.setPrevVertex_(vertexA2, vertexA1); // | | m_shape.setNextVertex_(vertexB2, vertexB1); // | | @@ -753,15 +706,6 @@ private void _resolveOverlapOddEven(boolean bDirection1, } } else// bDirection1 == bDirection2 { - if (!bDirection1) { - // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); - // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); - } else { - // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); - // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); - } - - // if (m_shape._RingParentageCheckInternal(vertexA1, vertexA2)) { int a1 = bDirection1 ? vertexA1 : vertexB1; int a2 = bDirection2 ? vertexA2 : vertexB2; @@ -861,7 +805,6 @@ private boolean _removeSpike(int vertexIn) { // m_shape.dbgVerifyIntegrity(vertex);//debug int vertex = vertexIn; - // _ASSERT(m_shape.isEqualXY(m_shape.getNextVertex(vertex), // m_shape.getPrevVertex(vertex))); boolean bFound = false; while (true) { @@ -984,61 +927,16 @@ private void _removeAngleSortInfo(int vertex) { } protected Simplificator() { - m_dbgCounter = 0; } public static boolean execute(EditShape shape, int geometry, int knownSimpleResult, boolean fixSelfTangency, ProgressTracker progressTracker) { Simplificator simplificator = new Simplificator(); simplificator.m_shape = shape; - // simplificator.m_bWinding = bWinding; simplificator.m_geometry = geometry; - simplificator.m_knownSimpleResult = knownSimpleResult; + //simplificator.m_knownSimpleResult = knownSimpleResult; simplificator.m_fixSelfTangency = fixSelfTangency; simplificator.m_progressTracker = progressTracker; return simplificator._simplify(); } - - int _compareVerticesSimple(int v1, int v2) { - Point2D pt1 = new Point2D(); - m_shape.getXY(v1, pt1); - Point2D pt2 = new Point2D(); - m_shape.getXY(v2, pt2); - int res = pt1.compare(pt2); - if (res == 0) {// sort equal vertices by the path ID - int i1 = m_shape.getPathFromVertex(v1); - int i2 = m_shape.getPathFromVertex(v2); - res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); - } - - return res; - } - - int _compareAngles(int index1, int index2) { - int vert1 = m_bunchEdgeEndPoints.get(index1); - Point2D pt1 = new Point2D(); - m_shape.getXY(vert1, pt1); - Point2D pt2 = new Point2D(); - int vert2 = m_bunchEdgeEndPoints.get(index2); - m_shape.getXY(vert2, pt2); - - if (pt1.isEqual(pt2)) - return 0;// overlap case - - int vert10 = m_bunchEdgeCenterPoints.get(index1); - Point2D pt10 = new Point2D(); - m_shape.getXY(vert10, pt10); - - int vert20 = m_bunchEdgeCenterPoints.get(index2); - Point2D pt20 = new Point2D(); - m_shape.getXY(vert20, pt20); - // _ASSERT(pt10.isEqual(pt20)); - - Point2D v1 = new Point2D(); - v1.sub(pt1, pt10); - Point2D v2 = new Point2D(); - v2.sub(pt2, pt20); - int result = Point2D._compareVectors(v1, v2); - return result; - } } From 48b9cbbce57d47cd354d60a8b8ef60288f25bdb4 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 6 Aug 2019 11:26:15 -0700 Subject: [PATCH 093/116] Fix unused variables and a typo (#231) --- .../core/geometry/RelationalOperations.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 0b73561a..abf03372 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -4142,9 +4142,6 @@ private static boolean linearPathIntersectsMultiPoint_( SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) .querySegmentIterator(); - boolean bContained = true; - boolean bInteriorHitFound = false; - Envelope2D env_a = new Envelope2D(); Envelope2D env_b = new Envelope2D(); Envelope2D envInter = new Envelope2D(); @@ -4152,10 +4149,6 @@ private static boolean linearPathIntersectsMultiPoint_( multipoint_b.queryEnvelope2D(env_b); env_a.inflate(tolerance, tolerance); - if (!env_a.contains(env_b)) { - bContained = false; - } - env_b.inflate(tolerance, tolerance); envInter.setCoords(env_a); envInter.intersect(env_b); @@ -4169,6 +4162,7 @@ private static boolean linearPathIntersectsMultiPoint_( if (accel != null) { quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); if (quadTreeA == null) { qtA = InternalUtils.buildQuadTree( (MultiPathImpl) multipathA._getImpl(), envInter); @@ -4187,7 +4181,6 @@ private static boolean linearPathIntersectsMultiPoint_( qtIterPathsA = quadTreePathsA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); - boolean b_intersects = false; double toleranceSq = tolerance * tolerance; for (int i = 0; i < multipoint_b.getPointCount(); i++) { @@ -5153,9 +5146,9 @@ private static final class OverlapEvent { double m_scalar_a_0; double m_scalar_a_1; int m_ivertex_b; - int m_ipath_b; - double m_scalar_b_0; - double m_scalar_b_1; +// int m_ipath_b; +// double m_scalar_b_0; +// double m_scalar_b_1; static OverlapEvent construct(int ivertex_a, int ipath_a, double scalar_a_0, double scalar_a_1, int ivertex_b, @@ -5166,9 +5159,9 @@ static OverlapEvent construct(int ivertex_a, int ipath_a, overlapEvent.m_scalar_a_0 = scalar_a_0; overlapEvent.m_scalar_a_1 = scalar_a_1; overlapEvent.m_ivertex_b = ivertex_b; - overlapEvent.m_ipath_b = ipath_b; - overlapEvent.m_scalar_b_0 = scalar_b_0; - overlapEvent.m_scalar_b_1 = scalar_b_1; +// overlapEvent.m_ipath_b = ipath_b; +// overlapEvent.m_scalar_b_0 = scalar_b_0; +// overlapEvent.m_scalar_b_1 = scalar_b_1; return overlapEvent; } } From 7812fd39290efdb19202b19f1d2f8924e2c9ecb7 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 15 Aug 2019 11:46:37 -0700 Subject: [PATCH 094/116] Don't return null buffer polygon (#243) --- .../java/com/esri/core/geometry/Bufferer.java | 10 +++++++- .../com/esri/core/geometry/TestBuffer.java | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index 7578a76c..f098e7e9 100755 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -576,6 +576,9 @@ private Geometry bufferPolygon_() { generateCircleTemplate_(); m_geometry = simplify.execute(m_geometry, null, false, m_progress_tracker); + if(m_geometry.isEmpty()) { + return m_geometry; + } if (m_distance < 0) { Polygon poly = (Polygon) (m_geometry); @@ -600,7 +603,12 @@ private Geometry bufferPolygon_() { .getInstance().getOperator(Operator.Type.Union)).execute( cursor, m_spatialReference, m_progress_tracker); Geometry result = union_cursor.next(); - return result; + if (result != null) { + return result; + } else { + //never return empty. + return new Polygon(m_geometry.getDescription()); + } } } diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java index 341751f5..e60145bb 100755 --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -27,6 +27,8 @@ import junit.framework.TestCase; import org.junit.Test; +import com.esri.core.geometry.ogc.OGCGeometry; + public class TestBuffer extends TestCase { @Override protected void setUp() throws Exception { @@ -389,4 +391,27 @@ public void testBufferPolygon() { assertTrue(simplify.isSimpleAsFeature(result, sr, null)); } } + + @Test + public static void testTinyBufferOfPoint() { + { + Geometry result1 = OperatorBuffer.local().execute(new Point(0, 0), SpatialReference.create(4326), 1e-9, null); + assertTrue(result1 != null); + assertTrue(result1.isEmpty()); + Geometry geom1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "POLYGON ((177.0 64.0, 177.0000000001 64.0, 177.0000000001 64.0000000001, 177.0 64.0000000001, 177.0 64.0))", null); + Geometry result2 = OperatorBuffer.local().execute(geom1, SpatialReference.create(4326), 0.01, null); + assertTrue(result2 != null); + assertTrue(result2.isEmpty()); + + } + + { + OGCGeometry p = OGCGeometry.fromText( + "POLYGON ((177.0 64.0, 177.0000000001 64.0, 177.0000000001 64.0000000001, 177.0 64.0000000001, 177.0 64.0))"); + OGCGeometry buffered = p.buffer(0.01); + assertTrue(buffered != null); + } + + + } } From 4205cd15ac9b453b905df9663520e379b59e81f7 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 19 Aug 2019 11:33:50 -0700 Subject: [PATCH 095/116] Fix hash calculation in few cases, change Point internals to be more compact (#238) --- .../core/geometry/AttributeStreamOfDbl.java | 2 +- .../java/com/esri/core/geometry/Envelope.java | 44 +-- .../com/esri/core/geometry/Envelope1D.java | 8 +- .../com/esri/core/geometry/Envelope2D.java | 25 +- .../com/esri/core/geometry/Envelope3D.java | 16 + .../java/com/esri/core/geometry/Line.java | 5 + .../geometry/MultiVertexGeometryImpl.java | 7 - .../com/esri/core/geometry/NumberUtils.java | 13 + .../java/com/esri/core/geometry/Point.java | 314 ++++++++---------- .../java/com/esri/core/geometry/Point2D.java | 11 +- .../java/com/esri/core/geometry/Point3D.java | 27 ++ .../java/com/esri/core/geometry/Segment.java | 19 +- .../java/com/esri/core/geometry/SizeOf.java | 2 +- .../com/esri/core/geometry/TestEnvelope.java | 73 ++-- .../com/esri/core/geometry/TestPoint.java | 28 +- 15 files changed, 333 insertions(+), 261 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 75d45a76..889121d2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -352,7 +352,7 @@ public boolean equals(AttributeStreamBase other, int start, int end) { end = size; for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) + if (!NumberUtils.isEqualNonIEEE(read(i), _other.read(i))) return false; return true; diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 98c46738..c254df1c 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -70,16 +70,20 @@ public Envelope(Envelope2D env2D) { public Envelope(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); + m_description = vd; m_envelope.setEmpty(); + _ensureAttributes(); } public Envelope(VertexDescription vd, Envelope2D env2D) { if (vd == null) throw new IllegalArgumentException(); + m_description = vd; m_envelope.setCoords(env2D); m_envelope.normalize(); + _ensureAttributes(); } /** @@ -331,8 +335,8 @@ void _setFromPoint(Point centerPoint, double width, double height) { } void _setFromPoint(Point centerPoint) { - m_envelope.setCoords(centerPoint.m_attributes[0], - centerPoint.m_attributes[1]); + mergeVertexDescription(centerPoint.getDescription()); + m_envelope.setCoords(centerPoint.getX(), centerPoint.getY()); VertexDescription pointVD = centerPoint.m_description; for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { int semantics = pointVD._getSemanticsImpl(iattrib); @@ -610,7 +614,6 @@ int getEndPointOffset(VertexDescription descr, int end_point) { throw new IllegalArgumentException(); int attribute_index = m_description.getAttributeIndex(semantics); - _ensureAttributes(); if (attribute_index >= 0) { return m_attributes[getEndPointOffset(m_description, end_point) + m_description.getPointAttributeOffset_(attribute_index) @@ -645,7 +648,6 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, throw new IllegalArgumentException(); addAttribute(semantics); - _ensureAttributes(); int attribute_index = m_description.getAttributeIndex(semantics); m_attributes[getEndPointOffset(m_description, end_point) + m_description.getPointAttributeOffset_(attribute_index) - 2 @@ -655,32 +657,17 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, void _ensureAttributes() { _touch(); if (m_attributes == null && m_description.getTotalComponentCount() > 2) { - m_attributes = new double[(m_description.getTotalComponentCount() - 2) * 2]; + int halfLength = m_description.getTotalComponentCount() - 2; + m_attributes = new double[halfLength * 2]; int offset0 = _getEndPointOffset(m_description, 0); int offset1 = _getEndPointOffset(m_description, 1); - - int j = 0; - for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - int semantics = m_description.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) - { - m_attributes[offset0 + j] = d; - m_attributes[offset1 + j] = d; - j++; - } - } + System.arraycopy(m_description._getDefaultPointAttributes(), 2, m_attributes, offset0, halfLength); + System.arraycopy(m_description._getDefaultPointAttributes(), 2, m_attributes, offset1, halfLength); } } @Override protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - if (newDescription.getTotalComponentCount() > 2) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); @@ -734,8 +721,6 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { throw new GeometryException( "This operation was performed on an Empty Geometry."); - // _ASSERT(endPoint == 0 || endPoint == 1); - if (semantics == Semantics.POSITION) { if (endPoint != 0) { return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; @@ -750,7 +735,6 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) { - _ensureAttributes(); return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; @@ -784,14 +768,10 @@ void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, throw new IndexOutOfBoundsException(); if (!hasAttribute(semantics)) { - if (VertexDescription.isDefaultValue(semantics, value)) - return; - addAttribute(semantics); } int attributeIndex = m_description.getAttributeIndex(semantics); - _ensureAttributes(); m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; @@ -1015,7 +995,7 @@ public boolean equals(Object _other) { return false; for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) - if (m_attributes[i] != other.m_attributes[i]) + if (!NumberUtils.isEqualNonIEEE(m_attributes[i], other.m_attributes[i])) return false; return true; @@ -1030,7 +1010,7 @@ public boolean equals(Object _other) { public int hashCode() { int hashCode = m_description.hashCode(); hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); - if (!isEmpty() && m_attributes != null) { + if (!isEmpty()) { for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) { hashCode = NumberUtils.hash(hashCode, m_attributes[i]); } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index c9d0d259..e000ef0a 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -225,7 +225,13 @@ public boolean equals(Object _other) @Override public int hashCode() { - return NumberUtils.hash(NumberUtils.hash(vmin), vmax); + if (isEmpty()) { + return NumberUtils.hash(NumberUtils.TheNaN); + } + + int hash = NumberUtils.hash(vmin); + hash = NumberUtils.hash(hash, vmax); + return hash; } } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 79433dd7..5c8bebf5 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -699,23 +699,14 @@ public boolean equals(Object _other) { @Override public int hashCode() { - - long bits = Double.doubleToLongBits(xmin); - int hc = (int) (bits ^ (bits >>> 32)); - - int hash = NumberUtils.hash(hc); - - bits = Double.doubleToLongBits(xmax); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); - - bits = Double.doubleToLongBits(ymin); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); - - bits = Double.doubleToLongBits(ymax); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); + if (isEmpty()) { + return NumberUtils.hash(NumberUtils.TheNaN); + } + + int hash = NumberUtils.hash(xmin); + hash = NumberUtils.hash(hash, xmax); + hash = NumberUtils.hash(hash, ymin); + hash = NumberUtils.hash(hash, ymax); return hash; } diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 3f64b053..6fa8b522 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -320,6 +320,22 @@ public boolean equals(Object _other) { return true; } + + @Override + public int hashCode() { + if (isEmpty()) { + return NumberUtils.hash(NumberUtils.TheNaN); + } + + int hash = NumberUtils.hash(xmin); + hash = NumberUtils.hash(hash, xmax); + hash = NumberUtils.hash(hash, ymin); + hash = NumberUtils.hash(hash, ymax); + hash = NumberUtils.hash(hash, zmin); + hash = NumberUtils.hash(hash, zmax); + return hash; + } + public void construct(Envelope1D xinterval, Envelope1D yinterval, Envelope1D zinterval) { if (xinterval.isEmpty() || yinterval.isEmpty()) { diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 90b08561..ce7b7b23 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -548,6 +548,11 @@ public boolean equals(Object other) { return _equalsImpl((Segment)other); } + @Override + public int hashCode() { + return super.hashCode(); + } + boolean equals(Line other) { if (other == this) return true; diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 6e847943..e045aa2a 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -168,8 +168,6 @@ public void getPointByVal(int index, Point dst) { Point outPoint = dst; outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { @@ -933,9 +931,6 @@ void _interpolateTwoVertices(int vertex1, int vertex2, double f, _verifyAllStreams(); outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); - for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { int semantics = m_description._getSemanticsImpl(attributeIndex); @@ -966,8 +961,6 @@ public Point getPoint(int index) { Point outPoint = new Point(); outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 4ee00a1e..01b6fd92 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -136,5 +136,18 @@ static int nextRand(int prevRand) { return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, // this is gcc's } + + /** + * Returns true if two values are equal (also can compare inf and nan). + */ + static boolean isEqualNonIEEE(double a, double b) { + return a == b || (Double.isNaN(a) && Double.isNaN(b)); + } + /** + * Returns true if two values are equal (also can compare inf and nan). + */ + static boolean isEqualNonIEEE(double a, double b, double tolerance) { + return a == b || Math.abs(a - b) <= tolerance || (Double.isNaN(a) && Double.isNaN(b)); + } } diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index a421400f..cc5b7042 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -39,19 +39,24 @@ public class Point extends Geometry implements Serializable { //We are using writeReplace instead. //private static final long serialVersionUID = 2L; - double[] m_attributes; // use doubles to store everything (long are bitcast) + private double m_x; + private double m_y; + private double[] m_attributes; // use doubles to store everything (long are bitcast) /** * Creates an empty 2D point. */ public Point() { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_x = NumberUtils.TheNaN; + m_y = NumberUtils.TheNaN; } public Point(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; + _setToDefault(); } /** @@ -68,6 +73,7 @@ public Point(double x, double y) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setXY(x, y); } + public Point(Point2D pt) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setXY(pt); @@ -91,19 +97,14 @@ public Point(double x, double y, double z) { Point3D pt = new Point3D(); pt.setCoords(x, y, z); setXYZ(pt); - } /** * Returns XY coordinates of this point. */ public final Point2D getXY() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - Point2D pt = new Point2D(); - pt.setCoords(m_attributes[0], m_attributes[1]); + pt.setCoords(m_x, m_y); return pt; } @@ -111,11 +112,7 @@ public final Point2D getXY() { * Returns XY coordinates of this point. */ public final void getXY(Point2D pt) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - pt.setCoords(m_attributes[0], m_attributes[1]); + pt.setCoords(m_x, m_y); } /** @@ -131,17 +128,10 @@ public final void setXY(Point2D pt) { * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. */ public Point3D getXYZ() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - Point3D pt = new Point3D(); - pt.x = m_attributes[0]; - pt.y = m_attributes[1]; - if (m_description.hasZ()) - pt.z = m_attributes[2]; - else - pt.z = VertexDescription.getDefaultValue(Semantics.Z); + pt.x = m_x; + pt.y = m_y; + pt.z = hasZ() ? m_attributes[0] : VertexDescription.getDefaultValue(VertexDescription.Semantics.Z); return pt; } @@ -154,39 +144,17 @@ public Point3D getXYZ() { */ public void setXYZ(Point3D pt) { _touch(); - boolean bHasZ = hasAttribute(Semantics.Z); - if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add - // Z - // only - // if - // pt.z - // is - // not - // a - // default - // value. - addAttribute(Semantics.Z); - bHasZ = true; - } - - if (m_attributes == null) - _setToDefault(); - - m_attributes[0] = pt.x; - m_attributes[1] = pt.y; - if (bHasZ) - m_attributes[2] = pt.z; + addAttribute(Semantics.Z); + m_x = pt.x; + m_y = pt.y; + m_attributes[0] = pt.z; } /** * Returns the X coordinate of the point. */ public final double getX() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - return m_attributes[0]; + return m_x; } /** @@ -196,18 +164,14 @@ public final double getX() { * The X coordinate to be set for this point. */ public void setX(double x) { - setAttribute(Semantics.POSITION, 0, x); + m_x = x; } /** * Returns the Y coordinate of this point. */ public final double getY() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - return m_attributes[1]; + return m_y; } /** @@ -217,14 +181,14 @@ public final double getY() { * The Y coordinate to be set for this point. */ public void setY(double y) { - setAttribute(Semantics.POSITION, 1, y); + m_y = y; } /** * Returns the Z coordinate of this point. */ public double getZ() { - return getAttributeAsDbl(Semantics.Z, 0); + return hasZ() ? m_attributes[0] : VertexDescription.getDefaultValue(VertexDescription.Semantics.Z); } /** @@ -282,10 +246,18 @@ public void setID(int id) { * @return The ordinate as double value. */ public double getAttributeAsDbl(int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - + if (semantics == VertexDescription.Semantics.POSITION) { + if (ordinate == 0) { + return m_x; + } + else if (ordinate == 1) { + return m_y; + } + else { + throw new IndexOutOfBoundsException(); + } + } + int ncomps = VertexDescription.getComponentCount(semantics); if (ordinate >= ncomps) throw new IndexOutOfBoundsException(); @@ -293,7 +265,7 @@ public double getAttributeAsDbl(int semantics, int ordinate) { int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) return m_attributes[m_description - ._getPointAttributeOffset(attributeIndex) + ordinate]; + ._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; else return VertexDescription.getDefaultValue(semantics); } @@ -310,20 +282,7 @@ public double getAttributeAsDbl(int semantics, int ordinate) { * @return The ordinate value truncated to a 32 bit integer value. */ public int getAttributeAsInt(int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex >= 0) - return (int) m_attributes[m_description - ._getPointAttributeOffset(attributeIndex) + ordinate]; - else - return (int) VertexDescription.getDefaultValue(semantics); + return (int)getAttributeAsDbl(semantics, ordinate); } /** @@ -340,6 +299,19 @@ public int getAttributeAsInt(int semantics, int ordinate) { */ public void setAttribute(int semantics, int ordinate, double value) { _touch(); + if (semantics == VertexDescription.Semantics.POSITION) { + if (ordinate == 0) { + m_x = value; + } + else if (ordinate == 1) { + m_y = value; + } + else { + throw new IndexOutOfBoundsException(); + } + return; + } + int ncomps = VertexDescription.getComponentCount(semantics); if (ncomps < ordinate) throw new IndexOutOfBoundsException(); @@ -350,10 +322,7 @@ public void setAttribute(int semantics, int ordinate, double value) { attributeIndex = m_description.getAttributeIndex(semantics); } - if (m_attributes == null) - _setToDefault(); - - m_attributes[m_description._getPointAttributeOffset(attributeIndex) + m_attributes[m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; } @@ -380,62 +349,70 @@ public long estimateMemorySize() @Override public void setEmpty() { _touch(); - if (m_attributes != null) { - m_attributes[0] = NumberUtils.NaN(); - m_attributes[1] = NumberUtils.NaN(); - } + _setToDefault(); } @Override protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[newDescription.getTotalComponentCount()]; - - int j = 0; - for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { - int semantics = newDescription.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - if (mapping[i] == -1) - { - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) + int newLen = newDescription.getTotalComponentCount() - 2; + if (newLen > 0) { + double[] newAttributes = new double[newLen]; + + int j = 0; + for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) { - newAttributes[j] = d; - j++; + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[j] = d; + j++; + } } - } - else { - int m = mapping[i]; - int offset = m_description._getPointAttributeOffset(m); - for (int ord = 0; ord < nords; ord++) - { - newAttributes[j] = m_attributes[offset]; - j++; - offset++; + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) + { + newAttributes[j] = m_attributes[offset]; + j++; + offset++; + } } + } - + + m_attributes = newAttributes; } - - m_attributes = newAttributes; + else { + m_attributes = null; + } + m_description = newDescription; } /** - * Sets the Point to a default, non-empty state. + * Sets to a default empty state. */ - void _setToDefault() { - resizeAttributes(m_description.getTotalComponentCount()); - Point.attributeCopy(m_description._getDefaultPointAttributes(), - m_attributes, m_description.getTotalComponentCount()); - m_attributes[0] = NumberUtils.NaN(); - m_attributes[1] = NumberUtils.NaN(); + private void _setToDefault() { + int len = m_description.getTotalComponentCount() - 2; + if (len != 0) { + if (m_attributes == null || m_attributes.length != len) { + m_attributes = new double[len]; + } + + System.arraycopy(m_description._getDefaultPointAttributes(), 2, m_attributes, 0, len); + } + else { + m_attributes = null; + } + + m_x = NumberUtils.TheNaN; + m_y = NumberUtils.TheNaN; } @Override @@ -449,7 +426,7 @@ public void applyTransformation(Transformation2D transform) { } @Override - void applyTransformation(Transformation3D transform) { + public void applyTransformation(Transformation3D transform) { if (isEmptyImpl()) return; @@ -462,20 +439,27 @@ void applyTransformation(Transformation3D transform) { public void copyTo(Geometry dst) { if (dst.getType() != Type.Point) throw new IllegalArgumentException(); - - Point pointDst = (Point) dst; + + if (this == dst) + return; + dst._touch(); - if (m_attributes == null) { - pointDst.setEmpty(); + Point pointDst = (Point) dst; + dst.m_description = m_description; + pointDst.m_x = m_x; + pointDst.m_y = m_y; + int attrLen = m_description.getTotalComponentCount() - 2; + if (attrLen == 0) { pointDst.m_attributes = null; - pointDst.assignVertexDescription(m_description); - } else { - pointDst.assignVertexDescription(m_description); - pointDst.resizeAttributes(m_description.getTotalComponentCount()); - attributeCopy(m_attributes, pointDst.m_attributes, - m_description.getTotalComponentCount()); + return; + } + + if (pointDst.m_attributes == null || pointDst.m_attributes.length != attrLen) { + pointDst.m_attributes = new double[attrLen]; } + + System.arraycopy(m_attributes, 0, pointDst.m_attributes, 0, attrLen); } @Override @@ -490,30 +474,29 @@ public boolean isEmpty() { } final boolean isEmptyImpl() { - return ((m_attributes == null) || NumberUtils.isNaN(m_attributes[0]) || NumberUtils - .isNaN(m_attributes[1])); + return NumberUtils.isNaN(m_x) || NumberUtils.isNaN(m_y); } @Override public void queryEnvelope(Envelope env) { - env.setEmpty(); if (m_description != env.m_description) env.assignVertexDescription(m_description); + + env.setEmpty(); env.merge(this); } @Override public void queryEnvelope2D(Envelope2D env) { - if (isEmptyImpl()) { env.setEmpty(); return; } - env.xmin = m_attributes[0]; - env.ymin = m_attributes[1]; - env.xmax = m_attributes[0]; - env.ymax = m_attributes[1]; + env.xmin = m_x; + env.ymin = m_y; + env.xmax = m_x; + env.ymax = m_y; } @Override @@ -523,13 +506,13 @@ void queryEnvelope3D(Envelope3D env) { return; } - Point3D pt = getXYZ(); - env.xmin = pt.x; - env.ymin = pt.y; - env.zmin = pt.z; - env.xmax = pt.x; - env.ymax = pt.y; - env.zmax = pt.z; + env.xmin = m_x; + env.ymin = m_y; + env.xmax = m_x; + env.ymax = m_y; + double z = getZ(); + env.zmin = z; + env.zmax = z; } @Override @@ -546,21 +529,6 @@ public Envelope1D queryInterval(int semantics, int ordinate) { return env; } - private void resizeAttributes(int newSize) { - if (m_attributes == null) { - m_attributes = new double[newSize]; - } else if (m_attributes.length < newSize) { - double[] newbuffer = new double[newSize]; - System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); - m_attributes = newbuffer; - } - } - - static void attributeCopy(double[] src, double[] dst, int count) { - if (count > 0) - System.arraycopy(src, 0, dst, 0, count); - } - /** * Set the X and Y coordinate of the point. * @@ -572,11 +540,8 @@ static void attributeCopy(double[] src, double[] dst, int count) { public void setXY(double x, double y) { _touch(); - if (m_attributes == null) - _setToDefault(); - - m_attributes[0] = x; - m_attributes[1] = y; + m_x = x; + m_y = y; } /** @@ -596,15 +561,21 @@ public boolean equals(Object _other) { if (m_description != otherPt.m_description) return false; - if (isEmptyImpl()) + if (isEmptyImpl()) { if (otherPt.isEmptyImpl()) return true; else return false; + } + + if (m_x != otherPt.m_x || m_y != otherPt.m_y) { + return false; + } - for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) - if (m_attributes[i] != otherPt.m_attributes[i]) + for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { + if (!NumberUtils.isEqualNonIEEE(m_attributes[i], otherPt.m_attributes[i])) return false; + } return true; } @@ -617,12 +588,15 @@ public boolean equals(Object _other) { public int hashCode() { int hashCode = m_description.hashCode(); if (!isEmptyImpl()) { - for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) { + hashCode = NumberUtils.hash(hashCode, m_x); + hashCode = NumberUtils.hash(hashCode, m_y); + for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { long bits = Double.doubleToLongBits(m_attributes[i]); int hc = (int) (bits ^ (bits >>> 32)); hashCode = NumberUtils.hash(hashCode, hc); } } + return hashCode; } diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 90cc1e46..95a46c66 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -94,6 +94,12 @@ public boolean equals(Object other) { return x == v.x && y == v.y; } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(x), y); + } + public void sub(Point2D other) { x -= other.x; @@ -751,11 +757,6 @@ static Point2D calculateCircleCenterFromThreePoints(Point2D from, Point2D mid_po } } - @Override - public int hashCode() { - return NumberUtils.hash(NumberUtils.hash(x), y); - } - double getAxis(int ordinate) { assert(ordinate == 0 || ordinate == 1); return (ordinate == 0 ? x : y); diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index 849b00e1..71cbfdba 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -132,4 +132,31 @@ boolean _isNan() { return NumberUtils.isNaN(x) || NumberUtils.isNaN(y) || NumberUtils.isNaN(z); } + public boolean equals(Point3D other) { + //note that for nan value this returns false. + //this is by design for this class. + return x == other.x && y == other.y && z == other.z; + } + + @Override + public boolean equals(Object other_) { + if (other_ == this) + return true; + + if (!(other_ instanceof Point3D)) + return false; + + Point3D other = (Point3D)other_; + //note that for nan value this returns false. + //this is by design for this class. + return x == other.x && y == other.y && z == other.z; + } + + @Override + public int hashCode() { + int hash = NumberUtils.hash(x); + hash = NumberUtils.hash(hash, y); + hash = NumberUtils.hash(hash, z); + return hash; + } } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index ca2ea184..b15364a2 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -528,9 +528,6 @@ private void _get(int endPoint, Point outPoint) { outPoint.assignVertexDescription(m_description); - if (outPoint.isEmptyImpl()) - outPoint._setToDefault(); - for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { int semantics = m_description._getSemanticsImpl(attributeIndex); @@ -689,12 +686,26 @@ boolean _equalsImpl(Segment other) { || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) return false; for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) - if (m_attributes[i] != other.m_attributes[i]) + if (!NumberUtils.isEqualNonIEEE(m_attributes[i], other.m_attributes[i])) return false; return true; } + @Override + public int hashCode() { + int hash = m_description.hashCode(); + hash = NumberUtils.hash(hash, m_xStart); + hash = NumberUtils.hash(hash, m_yStart); + hash = NumberUtils.hash(hash, m_xEnd); + hash = NumberUtils.hash(hash, m_yEnd); + for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) { + hash = NumberUtils.hash(hash, m_attributes[i]); + } + + return hash; + } + /** * Returns true, when this segment is a closed curve (start point is equal * to end point exactly). diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 6b097dad..8d9f4c77 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -68,7 +68,7 @@ public final class SizeOf { public static final int SIZE_OF_MULTI_POINT_IMPL = 56; - public static final int SIZE_OF_POINT = 24; + public static final int SIZE_OF_POINT = 40; public static final int SIZE_OF_POLYGON = 24; diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope.java b/src/test/java/com/esri/core/geometry/TestEnvelope.java index 56edd466..6b5622e8 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope.java @@ -21,29 +21,58 @@ public class TestEnvelope { - @Test - public void testIntersect() - { - assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); - assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(1, 1, 6, 6), new Envelope(1, 1, 5, 5)); - assertIntersection(new Envelope(1, 2, 3, 4), new Envelope(0, 0, 2, 3), new Envelope(1, 2, 2, 3)); + @Test + public void testIntersect() { + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(1, 1, 6, 6), new Envelope(1, 1, 5, 5)); + assertIntersection(new Envelope(1, 2, 3, 4), new Envelope(0, 0, 2, 3), new Envelope(1, 2, 2, 3)); - assertNoIntersection(new Envelope(), new Envelope()); - assertNoIntersection(new Envelope(0, 0, 5, 5), new Envelope()); - assertNoIntersection(new Envelope(), new Envelope(0, 0, 5, 5)); - } + assertNoIntersection(new Envelope(), new Envelope()); + assertNoIntersection(new Envelope(0, 0, 5, 5), new Envelope()); + assertNoIntersection(new Envelope(), new Envelope(0, 0, 5, 5)); + } - private static void assertIntersection(Envelope envelope, Envelope other, Envelope intersection) - { - boolean intersects = envelope.intersect(other); - assertTrue(intersects); - assertEquals(envelope, intersection); - } + @Test + public void testEquals() { + Envelope env1 = new Envelope(10, 9, 11, 12); + Envelope env2 = new Envelope(10, 9, 11, 13); + Envelope1D emptyInterval = new Envelope1D(); + emptyInterval.setEmpty(); + assertFalse(env1.equals(env2)); + env1.queryInterval(VertexDescription.Semantics.M, 0).equals(emptyInterval); + env2.setCoords(10, 9, 11, 12); + assertTrue(env1.equals(env2)); + env1.addAttribute(VertexDescription.Semantics.M); + env1.queryInterval(VertexDescription.Semantics.M, 0).equals(emptyInterval); + assertFalse(env1.equals(env2)); + env2.addAttribute(VertexDescription.Semantics.M); + assertTrue(env1.equals(env2)); + Envelope1D nonEmptyInterval = new Envelope1D(); + nonEmptyInterval.setCoords(1, 2); + env1.setInterval(VertexDescription.Semantics.M, 0, emptyInterval); + assertTrue(env1.equals(env2)); + env2.setInterval(VertexDescription.Semantics.M, 0, emptyInterval); + assertTrue(env1.equals(env2)); + env2.setInterval(VertexDescription.Semantics.M, 0, nonEmptyInterval); + assertFalse(env1.equals(env2)); + env1.setInterval(VertexDescription.Semantics.M, 0, nonEmptyInterval); + assertTrue(env1.equals(env2)); + env1.queryInterval(VertexDescription.Semantics.M, 0).equals(nonEmptyInterval); + env1.queryInterval(VertexDescription.Semantics.POSITION, 0).equals(new Envelope1D(10, 11)); + env1.queryInterval(VertexDescription.Semantics.POSITION, 0).equals(new Envelope1D(9, 13)); + } + + private static void assertIntersection(Envelope envelope, Envelope other, Envelope intersection) { + boolean intersects = envelope.intersect(other); + assertTrue(intersects); + assertEquals(envelope, intersection); + } - private static void assertNoIntersection(Envelope envelope, Envelope other) - { - boolean intersects = envelope.intersect(other); - assertFalse(intersects); - assertTrue(envelope.isEmpty()); - } + private static void assertNoIntersection(Envelope envelope, Envelope other) { + boolean intersects = envelope.intersect(other); + assertFalse(intersects); + assertTrue(envelope.isEmpty()); + } + } + diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index c2e8bd2f..c30f612f 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -45,9 +45,35 @@ protected void tearDown() throws Exception { public void testPt() { Point pt = new Point(); assertTrue(pt.isEmpty()); + assertTrue(Double.isNaN(pt.getX())); + assertTrue(Double.isNaN(pt.getY())); + assertTrue(Double.isNaN(pt.getM())); + assertTrue(pt.getZ() == 0); + Point pt1 = new Point(); + assertTrue(pt.equals(pt1)); + int hash1 = pt.hashCode(); pt.setXY(10, 2); assertFalse(pt.isEmpty()); - + assertTrue(pt.getX() == 10); + assertTrue(pt.getY() == 2); + assertTrue(pt.getXY().equals(new Point2D(10, 2))); + assertTrue(pt.getXYZ().x == 10); + assertTrue(pt.getXYZ().y == 2); + assertTrue(pt.getXYZ().z == 0); + assertFalse(pt.equals(pt1)); + pt.copyTo(pt1); + assertTrue(pt.equals(pt1)); + int hash2 = pt.hashCode(); + assertFalse(hash1 == hash2); + pt.setZ(5); + assertFalse(pt.equals(pt1)); + pt.copyTo(pt1); + assertTrue(pt.equals(pt1)); + assertFalse(hash1 == pt.hashCode()); + assertFalse(hash2 == pt.hashCode()); + assertTrue(pt.hasZ()); + assertTrue(pt.getZ() == 5); + assertTrue(pt.hasAttribute(VertexDescription.Semantics.Z)); pt.toString(); } From 961b53555259f691b96948418997d6698d3df950 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Thu, 29 Aug 2019 08:39:58 -0700 Subject: [PATCH 096/116] Geometry release v2.2.3 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4ea84c7..401315df 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.2 + 2.2.3 ``` diff --git a/pom.xml b/pom.xml index 09ac3899..a3dcbc19 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.3-SNAPSHOT + 2.2.3 jar Esri Geometry API for Java From 73a8be8d62f809b56ff432144aca468b3d1d9d2c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 21 Nov 2019 19:09:43 -0800 Subject: [PATCH 097/116] Fix self-tangency test typo (#248) --- .../geometry/OperatorSimplifyLocalHelper.java | 35 +++++++------------ .../java/com/esri/core/geometry/TestOGC.java | 9 +++++ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index b0bd4dd8..2513693e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -476,10 +476,12 @@ boolean checkSelfIntersectionsPolylinePlanar_() { || (xyindex == path_last); if (m_bOGCRestrictions) vi_prev.boundary = !is_closed_path && vi_prev.end_point; - else + else { // for regular planar simplify, only the end points are allowed // to coincide vi_prev.boundary = vi_prev.end_point; + } + vi_prev.ipath = ipath; vi_prev.x = pt.x; vi_prev.y = pt.y; @@ -506,11 +508,11 @@ boolean checkSelfIntersectionsPolylinePlanar_() { boolean end_point = (xyindex == path_start) || (xyindex == path_last); if (m_bOGCRestrictions) - boundary = !is_closed_path && vi_prev.end_point; + boundary = !is_closed_path && end_point; else // for regular planar simplify, only the end points are allowed // to coincide - boundary = vi_prev.end_point; + boundary = end_point; vi.x = pt.x; vi.y = pt.y; @@ -522,22 +524,12 @@ boolean checkSelfIntersectionsPolylinePlanar_() { if (vi.x == vi_prev.x && vi.y == vi_prev.y) { if (m_bOGCRestrictions) { if (!vi.boundary || !vi_prev.boundary) { + // check that this is not the endpoints of a closed path if ((vi.ipath != vi_prev.ipath) - || (!vi.end_point && !vi_prev.end_point))// check - // that - // this - // is - // not - // the - // endpoints - // of - // a - // closed - // path - { + || (!vi.end_point && !vi_prev.end_point)) { // one of coincident vertices is not on the boundary - // this is either Non_simple_result::cross_over or - // Non_simple_result::ogc_self_tangency. + // this is either NonSimpleResult.CrossOver or + // NonSimpleResult.OGCPolylineSelfTangency. // too expensive to distinguish between the two. m_nonSimpleResult = new NonSimpleResult( NonSimpleResult.Reason.OGCPolylineSelfTangency, @@ -546,12 +538,9 @@ boolean checkSelfIntersectionsPolylinePlanar_() { } } } else { - if (!vi.end_point || !vi_prev.end_point) {// one of - // coincident - // vertices is - // not an - // endpoint - m_nonSimpleResult = new NonSimpleResult( + if (!vi.end_point || !vi_prev.end_point) { + //one of coincident vertices is not an endpoint + m_nonSimpleResult = new NonSimpleResult( NonSimpleResult.Reason.CrossOver, vi.ivertex, vi_prev.ivertex); return false;// common point not on the boundary diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 34403661..f55dbb21 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1036,4 +1036,13 @@ public void testFlattened() { ogcGeometry = (OGCConcreteGeometryCollection)OGCGeometry.fromText("GEOMETRYCOLLECTION (MULTIPOINT (1 1), MULTILINESTRING ((1 2, 3 4)), MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2))))"); assertTrue(ogcGeometry.isFlattened()); } + + @Test + public void testIssue247IsSimple() { + //https://github.com/Esri/geometry-api-java/issues/247 + String wkt = "MULTILINESTRING ((-103.4894322 25.6164519, -103.4889647 25.6159054, -103.489434 25.615654), (-103.489434 25.615654, -103.4894322 25.6164519), (-103.4897361 25.6168342, -103.4894322 25.6164519))"; + OGCGeometry ogcGeom = OGCGeometry.fromText(wkt); + boolean b = ogcGeom.isSimple(); + assertTrue(b); + } } From abf6b7c6b825788b866266e2aad834f7309bab42 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 6 Dec 2019 12:19:14 -0800 Subject: [PATCH 098/116] close a stream in debug methods (#252) --- .../core/geometry/OperatorFactoryLocal.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 153b4728..04368b9d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -29,6 +29,7 @@ import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; @@ -182,20 +183,28 @@ public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { } String jsonString = null; + Reader reader = null; try { FileInputStream stream = new FileInputStream(file_name); - Reader reader = new BufferedReader(new InputStreamReader(stream)); + reader = new BufferedReader(new InputStreamReader(stream)); StringBuilder builder = new StringBuilder(); char[] buffer = new char[8192]; int read; while ((read = reader.read(buffer, 0, buffer.length)) > 0) { builder.append(buffer, 0, read); } - stream.close(); jsonString = builder.toString(); } catch (Exception ex) { } + finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); return mapGeom; @@ -276,20 +285,28 @@ public static Geometry loadGeometryFromWKTFileDbg(String file_name) { } String s = null; + Reader reader = null; try { FileInputStream stream = new FileInputStream(file_name); - Reader reader = new BufferedReader(new InputStreamReader(stream)); + reader = new BufferedReader(new InputStreamReader(stream)); StringBuilder builder = new StringBuilder(); char[] buffer = new char[8192]; int read; while ((read = reader.read(buffer, 0, buffer.length)) > 0) { builder.append(buffer, 0, read); } - stream.close(); s = builder.toString(); } catch (Exception ex) { } + finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } return OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); } From 4c2fbd3d35e83c39544e85b3ca9fec57b8c03566 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 14 Jan 2020 09:18:29 -0800 Subject: [PATCH 099/116] Make Transformation3D class public (#254) --- src/main/java/com/esri/core/geometry/Transformation3D.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 99702706..cac98407 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -33,7 +33,7 @@ * are the matrices. This is equivalent to the following line of code: * ResultVector = (M1.Mul(M2).Mul(M3)).Transform(Vector) */ -final class Transformation3D { +final public class Transformation3D { public double xx, yx, zx, xd, xy, yy, zy, yd, xz, yz, zz, zd; @@ -112,7 +112,7 @@ public Envelope3D transform(Envelope3D env) { return env; } - void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { + public void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { for (int i = 0; i < count; i++) { Point3D res = new Point3D(); Point3D src = pointsIn[i]; From 37b7635d9c1f3d1f4119191272a8e8406a379d0d Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 15 Jan 2020 20:04:29 -0800 Subject: [PATCH 100/116] Add a unit test and a fix for the crash in cut (#255) --- .../java/com/esri/core/geometry/Cutter.java | 5 +- .../java/com/esri/core/geometry/TestCut.java | 54 ++++++++++++------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index f56dc5de..3c34c064 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -27,7 +27,6 @@ import com.esri.core.geometry.OperatorCutLocal; -import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; @@ -138,6 +137,8 @@ static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, private static ArrayList _getCutEvents(int orderIndex, EditShape editShape) { int pointCount = editShape.getTotalPointCount(); + if (pointCount == 0) + return null; // Sort vertices lexicographically // Firstly copy allvertices to an array. @@ -156,8 +157,6 @@ private static ArrayList _getCutEvents(int orderIndex, CompareVertices compareVertices = new CompareVertices(orderIndex, editShape); vertices.Sort(0, pointCount, new CutterVertexComparer(compareVertices)); - // SORTDYNAMICARRAYEX(vertices, index_type, 0, pointCount, - // CutterVertexComparer, compareVertices); // Find Cut Events ArrayList cutEvents = new ArrayList(0); diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index 456973cd..a388ff01 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -51,7 +51,7 @@ public static void testCut4326() { } - public static void testConsiderTouch1(SpatialReference spatialReference) { + private static void testConsiderTouch1(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -101,7 +101,7 @@ public static void testConsiderTouch1(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testConsiderTouch2(SpatialReference spatialReference) { + private static void testConsiderTouch2(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -167,7 +167,7 @@ public static void testConsiderTouch2(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon5(SpatialReference spatialReference) { + private static void testPolygon5(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -201,7 +201,7 @@ public static void testPolygon5(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon7(SpatialReference spatialReference) { + private static void testPolygon7(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -238,7 +238,7 @@ public static void testPolygon7(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon8(SpatialReference spatialReference) { + private static void testPolygon8(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -275,7 +275,7 @@ public static void testPolygon8(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon9(SpatialReference spatialReference) { + private static void testPolygon9(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -309,7 +309,7 @@ public static void testPolygon9(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testEngine(SpatialReference spatialReference) { + private static void testEngine(SpatialReference spatialReference) { Polygon polygon8 = makePolygon8(); Polyline cutter8 = makePolygonCutter8(); @@ -337,7 +337,7 @@ public static void testEngine(SpatialReference spatialReference) { assertTrue(area == 800); } - public static Polyline makePolyline1() { + private static Polyline makePolyline1() { Polyline poly = new Polyline(); poly.startPath(0, 0); @@ -355,7 +355,7 @@ public static Polyline makePolyline1() { return poly; } - public static Polyline makePolylineCutter1() { + private static Polyline makePolylineCutter1() { Polyline poly = new Polyline(); poly.startPath(1, 0); @@ -395,7 +395,7 @@ public static Polyline makePolylineCutter1() { return poly; } - public static Polyline makePolyline2() { + private static Polyline makePolyline2() { Polyline poly = new Polyline(); poly.startPath(-2, 0); @@ -410,7 +410,7 @@ public static Polyline makePolyline2() { return poly; } - public static Polyline makePolylineCutter2() { + private static Polyline makePolylineCutter2() { Polyline poly = new Polyline(); poly.startPath(-1.5, 0); @@ -443,7 +443,7 @@ public static Polyline makePolylineCutter2() { return poly; } - public static Polygon makePolygon5() { + private static Polygon makePolygon5() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -454,7 +454,7 @@ public static Polygon makePolygon5() { return poly; } - public static Polyline makePolygonCutter5() { + private static Polyline makePolygonCutter5() { Polyline poly = new Polyline(); poly.startPath(15, 0); @@ -466,7 +466,7 @@ public static Polyline makePolygonCutter5() { return poly; } - public static Polygon makePolygon7() { + private static Polygon makePolygon7() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -477,7 +477,7 @@ public static Polygon makePolygon7() { return poly; } - public static Polyline makePolygonCutter7() { + private static Polyline makePolygonCutter7() { Polyline poly = new Polyline(); poly.startPath(10, 10); @@ -489,7 +489,7 @@ public static Polyline makePolygonCutter7() { return poly; } - public static Polygon makePolygon8() { + private static Polygon makePolygon8() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -500,7 +500,7 @@ public static Polygon makePolygon8() { return poly; } - public static Polyline makePolygonCutter8() { + private static Polyline makePolygonCutter8() { Polyline poly = new Polyline(); poly.startPath(10, 10); @@ -512,7 +512,7 @@ public static Polyline makePolygonCutter8() { return poly; } - public static Polygon makePolygon9() { + private static Polygon makePolygon9() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -533,7 +533,7 @@ public static Polygon makePolygon9() { return poly; } - public static Polyline makePolygonCutter9() { + private static Polyline makePolygonCutter9() { Polyline poly = new Polyline(); poly.startPath(5, -1); @@ -541,4 +541,20 @@ public static Polyline makePolygonCutter9() { return poly; } + + @Test + public void testGithubIssue253() { + //https://github.com/Esri/geometry-api-java/issues/253 + SpatialReference spatialReference = SpatialReference.create(3857); + Polyline poly1 = new Polyline(); + poly1.startPath(610, 552); + poly1.lineTo(610, 552); + Polyline poly2 = new Polyline(); + poly2.startPath(610, 552); + poly2.lineTo(610, 552); + GeometryCursor cursor = OperatorCut.local().execute(true, poly1, poly2, spatialReference, null); + + Geometry res = cursor.next(); + assertTrue(res == null); + } } From 6add959ef028b993607e0a97a507d8b3bffa1f08 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 12 Aug 2020 10:33:50 -0700 Subject: [PATCH 101/116] Fix a hang in union of a point with polyline (#267) --- .../esri/core/geometry/PlaneSweepCrackerHelper.java | 9 +++++++-- src/test/java/com/esri/core/geometry/TestOGC.java | 11 +++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 4bdf00cc..c84b4724 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -1212,9 +1212,14 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, // Adjust the vertex coordinates and split the segments in the the edit // shape. applyIntersectorToEditShape_(edgeOrigins1, intersector, 0); - if (edge2 != -1) + if (edgeOrigins2 != -1) applyIntersectorToEditShape_(edgeOrigins2, intersector, 1); - + else { + assert (intersectionCluster != -1); + Point2D pt = intersector.getResultPoint().getXY(); + updateClusterXY(intersectionCluster, pt); + } + // Produce clusters, and new edges. The new edges are added to // m_edges_to_insert_in_sweep_structure. createEdgesAndClustersFromSplitEdge_(edge1, intersector, 0); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index f55dbb21..b68f2fcc 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1045,4 +1045,15 @@ public void testIssue247IsSimple() { boolean b = ogcGeom.isSimple(); assertTrue(b); } + + @Test + public void testOGCUnionLinePoint() { + OGCGeometry point = OGCGeometry.fromText("POINT (-44.16176186699087 -19.943264803833348)"); + OGCGeometry lineString = OGCGeometry.fromText( + "LINESTRING (-44.1247493 -19.9467657, -44.1247979 -19.9468385, -44.1249043 -19.946934, -44.1251096 -19.9470651, -44.1252609 -19.9471383, -44.1254992 -19.947204, -44.1257652 -19.947229, -44.1261292 -19.9471833, -44.1268946 -19.9470098, -44.1276847 -19.9468416, -44.127831 -19.9468143, -44.1282639 -19.9467366, -44.1284569 -19.9467237, -44.1287119 -19.9467261, -44.1289437 -19.9467665, -44.1291499 -19.9468221, -44.1293856 -19.9469396, -44.1298857 -19.9471497, -44.1300908 -19.9472071, -44.1302743 -19.9472331, -44.1305029 -19.9472364, -44.1306498 -19.9472275, -44.1308054 -19.947216, -44.1308553 -19.9472037, -44.1313206 -19.9471394, -44.1317889 -19.9470854, -44.1330422 -19.9468887, -44.1337465 -19.9467083, -44.1339922 -19.9466842, -44.1341506 -19.9466997, -44.1343621 -19.9467226, -44.1345134 -19.9467855, -44.1346494 -19.9468456, -44.1347295 -19.946881, -44.1347988 -19.9469299, -44.1350231 -19.9471131, -44.1355843 -19.9478307, -44.1357802 -19.9480557, -44.1366289 -19.949198, -44.1370384 -19.9497001, -44.137386 -19.9501921, -44.1374113 -19.9502263, -44.1380888 -19.9510925, -44.1381769 -19.9513526, -44.1382509 -19.9516202, -44.1383014 -19.9522136, -44.1383889 -19.9530931, -44.1384227 -19.9538784, -44.1384512 -19.9539653, -44.1384555 -19.9539807, -44.1384901 -19.9541928, -44.1385563 -19.9543859, -44.1386656 -19.9545781, -44.1387339 -19.9546889, -44.1389219 -19.9548661, -44.1391695 -19.9550384, -44.1393672 -19.9551414, -44.1397538 -19.9552208, -44.1401714 -19.9552332, -44.1405656 -19.9551143, -44.1406198 -19.9550853, -44.1407579 -19.9550224, -44.1409029 -19.9549201, -44.1410283 -19.9548257, -44.1413902 -19.9544132, -44.141835 -19.9539274, -44.142268 -19.953484, -44.1427036 -19.9531023, -44.1436229 -19.952259, -44.1437568 -19.9521565, -44.1441783 -19.9517273, -44.144644 -19.9512109, -44.1452538 -19.9505663, -44.1453541 -19.9504774, -44.1458653 -19.9500442, -44.1463563 -19.9496473, -44.1467534 -19.9492812, -44.1470553 -19.9490028, -44.1475804 -19.9485293, -44.1479838 -19.9482096, -44.1485003 -19.9478532, -44.1489451 -19.9477314, -44.1492225 -19.9477024, -44.149453 -19.9476684, -44.149694 -19.9476387, -44.1499556 -19.9475436, -44.1501398 -19.9474234, -44.1502723 -19.9473206, -44.150421 -19.9471473, -44.1505043 -19.9470004, -44.1507664 -19.9462594, -44.150867 -19.9459518, -44.1509225 -19.9457843, -44.1511168 -19.945466, -44.1513601 -19.9452272, -44.1516846 -19.944999, -44.15197 -19.9448738, -44.1525994 -19.9447263, -44.1536614 -19.9444791, -44.1544071 -19.9442671, -44.1548978 -19.9441275, -44.1556247 -19.9438304, -44.1565996 -19.9434083, -44.1570351 -19.9432556, -44.1573142 -19.9432091, -44.1575332 -19.9431645, -44.157931 -19.9431484, -44.1586408 -19.9431504, -44.1593575 -19.9431457, -44.1596498 -19.9431562, -44.1600991 -19.9431475, -44.1602331 -19.9431567, -44.1607926 -19.9432449, -44.1609723 -19.9432499, -44.1623815 -19.9432765, -44.1628299 -19.9433645, -44.1632475 -19.9435839, -44.1633456 -19.9436559, -44.1636261 -19.9439375, -44.1638186 -19.9442439, -44.1642535 -19.9451781, -44.165178 -19.947156, -44.1652928 -19.9474016, -44.1653074 -19.9474329, -44.1654026 -19.947766, -44.1654774 -19.9481718, -44.1655699 -19.9490241, -44.1656196 -19.9491538, -44.1659735 -19.9499097, -44.1662485 -19.9504925, -44.1662996 -19.9506347, -44.1663574 -19.9512961, -44.1664094 -19.9519273, -44.1664144 -19.9519881, -44.1664799 -19.9526399, -44.1666965 -19.9532586, -44.1671191 -19.9544126, -44.1672019 -19.9545869, -44.1673344 -19.9547603, -44.1675958 -19.9550466, -44.1692349 -19.9567775, -44.1694607 -19.9569284, -44.1718843 -19.9574147, -44.1719167 -19.9574206, -44.1721627 -19.9574748, -44.1723207 -19.9575386, -44.1724439 -19.9575883, -44.1742798 -19.9583293, -44.1748841 -19.9585688, -44.1751118 -19.9586796, -44.1752554 -19.9587769, -44.1752644 -19.9587881, -44.1756052 -19.9592143, -44.1766415 -19.9602689, -44.1774912 -19.9612387, -44.177663 -19.961364, -44.177856 -19.9614494, -44.178034 -19.9615125, -44.1782475 -19.9615423, -44.1785115 -19.9615155, -44.1795404 -19.9610879, -44.1796393 -19.9610759, -44.1798873 -19.9610459, -44.1802404 -19.961036, -44.1804714 -19.9609634, -44.181059 -19.9605365, -44.1815113 -19.9602333, -44.1826712 -19.9594067, -44.1829715 -19.9592551, -44.1837201 -19.9590611, -44.1839277 -19.9590073, -44.1853022 -19.9586512, -44.1856812 -19.9585316, -44.1862915 -19.9584212, -44.1866215 -19.9583494, -44.1867651 -19.9583391, -44.1868852 -19.9583372, -44.1872523 -19.9583313, -44.187823 -19.9583281, -44.1884457 -19.958351, -44.1889559 -19.958437, -44.1893825 -19.9585816, -44.1897582 -19.9587828, -44.1901186 -19.9590453, -44.1912457 -19.9602029, -44.1916575 -19.9606307, -44.1921624 -19.9611588, -44.1925367 -19.9615872, -44.1931832 -19.9622566, -44.1938468 -19.9629343, -44.194089 -19.9631996, -44.1943924 -19.9634141, -44.1946006 -19.9635104, -44.1948789 -19.963599, -44.1957402 -19.9637569, -44.1964094 -19.9638505, -44.1965875 -19.9639188, -44.1967865 -19.9640801, -44.197096 -19.9643572, -44.1972765 -19.964458, -44.1974407 -19.9644824, -44.1976234 -19.9644668, -44.1977654 -19.9644282, -44.1980715 -19.96417, -44.1984541 -19.9638069, -44.1986632 -19.9636002, -44.1988132 -19.9634172, -44.1989542 -19.9632962, -44.1991349 -19.9631081)"); + OGCGeometry result12 = point.union(lineString); + String text12 = result12.asText(); + assertEquals(text12, "LINESTRING (-44.1247493 -19.9467657, -44.1247979 -19.9468385, -44.1249043 -19.946934, -44.1251096 -19.9470651, -44.1252609 -19.9471383, -44.1254992 -19.947204, -44.1257652 -19.947229, -44.1261292 -19.9471833, -44.1268946 -19.9470098, -44.1276847 -19.9468416, -44.127831 -19.9468143, -44.1282639 -19.9467366, -44.1284569 -19.9467237, -44.1287119 -19.9467261, -44.1289437 -19.9467665, -44.1291499 -19.9468221, -44.1293856 -19.9469396, -44.1298857 -19.9471497, -44.1300908 -19.9472071, -44.1302743 -19.9472331, -44.1305029 -19.9472364, -44.1306498 -19.9472275, -44.1308054 -19.947216, -44.1308553 -19.9472037, -44.1313206 -19.9471394, -44.1317889 -19.9470854, -44.1330422 -19.9468887, -44.1337465 -19.9467083, -44.1339922 -19.9466842, -44.1341506 -19.9466997, -44.1343621 -19.9467226, -44.1345134 -19.9467855, -44.1346494 -19.9468456, -44.1347295 -19.946881, -44.1347988 -19.9469299, -44.1350231 -19.9471131, -44.1355843 -19.9478307, -44.1357802 -19.9480557, -44.1366289 -19.949198, -44.1370384 -19.9497001, -44.137386 -19.9501921, -44.1374113 -19.9502263, -44.1380888 -19.9510925, -44.1381769 -19.9513526, -44.1382509 -19.9516202, -44.1383014 -19.9522136, -44.1383889 -19.9530931, -44.1384227 -19.9538784, -44.1384512 -19.9539653, -44.1384555 -19.9539807, -44.1384901 -19.9541928, -44.1385563 -19.9543859, -44.1386656 -19.9545781, -44.1387339 -19.9546889, -44.1389219 -19.9548661, -44.1391695 -19.9550384, -44.1393672 -19.9551414, -44.1397538 -19.9552208, -44.1401714 -19.9552332, -44.1405656 -19.9551143, -44.1406198 -19.9550853, -44.1407579 -19.9550224, -44.1409029 -19.9549201, -44.1410283 -19.9548257, -44.1413902 -19.9544132, -44.141835 -19.9539274, -44.142268 -19.953484, -44.1427036 -19.9531023, -44.1436229 -19.952259, -44.1437568 -19.9521565, -44.1441783 -19.9517273, -44.144644 -19.9512109, -44.1452538 -19.9505663, -44.1453541 -19.9504774, -44.1458653 -19.9500442, -44.1463563 -19.9496473, -44.1467534 -19.9492812, -44.1470553 -19.9490028, -44.1475804 -19.9485293, -44.1479838 -19.9482096, -44.1485003 -19.9478532, -44.1489451 -19.9477314, -44.1492225 -19.9477024, -44.149453 -19.9476684, -44.149694 -19.9476387, -44.1499556 -19.9475436, -44.1501398 -19.9474234, -44.1502723 -19.9473206, -44.150421 -19.9471473, -44.1505043 -19.9470004, -44.1507664 -19.9462594, -44.150867 -19.9459518, -44.1509225 -19.9457843, -44.1511168 -19.945466, -44.1513601 -19.9452272, -44.1516846 -19.944999, -44.15197 -19.9448738, -44.1525994 -19.9447263, -44.1536614 -19.9444791, -44.1544071 -19.9442671, -44.1548978 -19.9441275, -44.1556247 -19.9438304, -44.1565996 -19.9434083, -44.1570351 -19.9432556, -44.1573142 -19.9432091, -44.1575332 -19.9431645, -44.157931 -19.9431484, -44.1586408 -19.9431504, -44.1593575 -19.9431457, -44.1596498 -19.9431562, -44.1600991 -19.9431475, -44.1602331 -19.9431567, -44.1607926 -19.9432449, -44.1609723 -19.9432499, -44.16176186699087 -19.94326480383335, -44.1623815 -19.9432765, -44.1628299 -19.9433645, -44.1632475 -19.9435839, -44.1633456 -19.9436559, -44.1636261 -19.9439375, -44.1638186 -19.9442439, -44.1642535 -19.9451781, -44.165178 -19.947156, -44.1652928 -19.9474016, -44.1653074 -19.9474329, -44.1654026 -19.947766, -44.1654774 -19.9481718, -44.1655699 -19.9490241, -44.1656196 -19.9491538, -44.1659735 -19.9499097, -44.1662485 -19.9504925, -44.1662996 -19.9506347, -44.1663574 -19.9512961, -44.1664094 -19.9519273, -44.1664144 -19.9519881, -44.1664799 -19.9526399, -44.1666965 -19.9532586, -44.1671191 -19.9544126, -44.1672019 -19.9545869, -44.1673344 -19.9547603, -44.1675958 -19.9550466, -44.1692349 -19.9567775, -44.1694607 -19.9569284, -44.1718843 -19.9574147, -44.1719167 -19.9574206, -44.1721627 -19.9574748, -44.1723207 -19.9575386, -44.1724439 -19.9575883, -44.1742798 -19.9583293, -44.1748841 -19.9585688, -44.1751118 -19.9586796, -44.1752554 -19.9587769, -44.1752644 -19.9587881, -44.1756052 -19.9592143, -44.1766415 -19.9602689, -44.1774912 -19.9612387, -44.177663 -19.961364, -44.177856 -19.9614494, -44.178034 -19.9615125, -44.1782475 -19.9615423, -44.1785115 -19.9615155, -44.1795404 -19.9610879, -44.1796393 -19.9610759, -44.1798873 -19.9610459, -44.1802404 -19.961036, -44.1804714 -19.9609634, -44.181059 -19.9605365, -44.1815113 -19.9602333, -44.1826712 -19.9594067, -44.1829715 -19.9592551, -44.1837201 -19.9590611, -44.1839277 -19.9590073, -44.1853022 -19.9586512, -44.1856812 -19.9585316, -44.1862915 -19.9584212, -44.1866215 -19.9583494, -44.1867651 -19.9583391, -44.1868852 -19.9583372, -44.1872523 -19.9583313, -44.187823 -19.9583281, -44.1884457 -19.958351, -44.1889559 -19.958437, -44.1893825 -19.9585816, -44.1897582 -19.9587828, -44.1901186 -19.9590453, -44.1912457 -19.9602029, -44.1916575 -19.9606307, -44.1921624 -19.9611588, -44.1925367 -19.9615872, -44.1931832 -19.9622566, -44.1938468 -19.9629343, -44.194089 -19.9631996, -44.1943924 -19.9634141, -44.1946006 -19.9635104, -44.1948789 -19.963599, -44.1957402 -19.9637569, -44.1964094 -19.9638505, -44.1965875 -19.9639188, -44.1967865 -19.9640801, -44.197096 -19.9643572, -44.1972765 -19.964458, -44.1974407 -19.9644824, -44.1976234 -19.9644668, -44.1977654 -19.9644282, -44.1980715 -19.96417, -44.1984541 -19.9638069, -44.1986632 -19.9636002, -44.1988132 -19.9634172, -44.1989542 -19.9632962, -44.1991349 -19.9631081)"); + } + } From d5a9a3fcf90626cbd1381cf48d3e9c2e6abe5422 Mon Sep 17 00:00:00 2001 From: johansettlin <37585850+johansettlin@users.noreply.github.com> Date: Wed, 12 Aug 2020 19:36:26 +0200 Subject: [PATCH 102/116] Test cases see issue #5 (#259) Add unit test for: - Envelope: getCenter() & Merge() - Envelope2D: clipLine() & sqrDistances() --- .../com/esri/core/geometry/TestEnvelope.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope.java b/src/test/java/com/esri/core/geometry/TestEnvelope.java index 6b5622e8..9ee34862 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope.java @@ -21,6 +21,107 @@ public class TestEnvelope { + @Test + /**The function returns the x and y coordinates of the center of the envelope. + * If the envelope is empty the point is set to empty otherwise the point is set to the center of the envelope. + */ + public void testGetCeneter(){ + //xmin,ymin,xmax,ymax of envelope + Envelope env1 = new Envelope(1,1, 2, 4); + Envelope env2 = new Envelope(); + Point p = new Point(); + Point p1 = new Point(1,2); + + /**Tests if the point is correctly set to the center of the envelope, */ + env1.getCenter(p); + assertTrue(p.getX() == 1.5); + + /** Tests if the point is empty because of the envelope is empty */ + env2.getCenter(p1); + assertTrue(p1.isEmpty()); + } + @Test + /* Merge takes a Point as input and increas the bouandary of the envelope to contain the point. + *If the point is empty the envelope remains the same or if the envelope is empty the coordinates + *of the point is assigned to the envelope */ + public void testMerge(){ + + /* To increase the covarege the branch where the envelope is empty can be tested + * And that the envelope and the point is not empty */ + Envelope env1 = new Envelope(1,1, 2, 4); + Envelope env2 = new Envelope(1,1, 2, 4); + Envelope env3 = new Envelope(1,1, 2, 4); + Point p = new Point(100,4); + + /*This should be false since env1 should change depending on point p */ + env1.merge(p); + assertFalse(env1.equals(env2)); + + /* This assert should be true since the point is empty and therefore env2 should not change */ + Point p1 = new Point(); + env2.merge(p1); + assertTrue(env2.equals(env3)); + } + + + @Test + /** TESTEST ENVELOPE2D ** + * ClipLine modify a line to be inside a envelope if possible */ + public void TestClipLine(){ + + //checking if segPrama is 0 and the segment is outside of the clipping window + //covers first return + Envelope2D env0 = new Envelope2D(1, 1, 4, 4); + // Reaches the branch where the delta is 0 + Point2D p1 = new Point2D(2,2); + Point2D p2 = new Point2D(2,2); + + int lineExtension = 0; + double[] segParams = {3,4}; + //reaches the branch where boundaryDistances is not 0 + double[] boundaryDistances = {2.0, 3.0}; + + int a = env0.clipLine(p1, p2, lineExtension, segParams, boundaryDistances); + //should be true since the points are inside the envelope + assertTrue(a == 4); + + // Changes p3 to fit the envelop, the line is on the edge of the envelope + Envelope2D env1 = new Envelope2D(1, 1, 4, 4); + Point2D p3 = new Point2D(1,10); + Point2D p4 = new Point2D(1,1); + + int b = env1.clipLine(p3, p4, lineExtension, segParams, boundaryDistances); + assertTrue(b == 1); + // the second point is outside and therefore changed + Envelope2D env2 = new Envelope2D(1, 1, 4, 4); + Point2D p5 = new Point2D(2,2); + Point2D p6 = new Point2D(1,10); + + int c = env2.clipLine(p5, p6, lineExtension, segParams, boundaryDistances); + assertTrue(c == 2); + + //Both points is outside the envelope and therefore no line is possible to clip, and this should return 0 + Envelope2D env3 = new Envelope2D(1, 1, 4, 4); + Point2D p7 = new Point2D(11,10); + Point2D p8 = new Point2D(5,5); + + int d = env3.clipLine(p7, p8, lineExtension, segParams, boundaryDistances); + assertTrue(d == 0); + } + + @Test + public void testSqrDistances(){ + //the point is on the envelope, which means that the distance is 0 + Envelope2D env0 = new Envelope2D(1, 1, 4, 4); + Point2D p0 = new Point2D(4,4); + assertTrue(env0.sqrDistance(p0) == 0.0); + + Envelope2D env1 = new Envelope2D(1, 1, 4, 4); + Point2D p1 = new Point2D(1,0); + + assertTrue(env0.sqrDistance(p1) == 1.0); + + } @Test public void testIntersect() { assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); From 222c0e0d4cb0d4dc7dc02a4c417d7dc516f36a51 Mon Sep 17 00:00:00 2001 From: satish-csi <67928686+satish-csi@users.noreply.github.com> Date: Tue, 1 Sep 2020 03:14:03 +0530 Subject: [PATCH 103/116] Fix Geometry WKB parsing issue For MultiPoint ZM geometry (#268) --- .../com/esri/core/geometry/OperatorExportToWkbLocal.java | 2 +- src/test/java/com/esri/core/geometry/TestWKBSupport.java | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index bb3abaad..a1ddec68 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -750,7 +750,7 @@ else if (wkbBuffer.capacity() < (int) size) if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { wkbBuffer.put(offset, byteOrder); offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZM); offset += 4; wkbBuffer.putInt(offset, point_count); offset += 4; diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index a55252fb..dfbaba16 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import com.esri.core.geometry.ogc.OGCGeometry; import java.io.IOException; import java.nio.ByteBuffer; import junit.framework.TestCase; @@ -107,4 +108,12 @@ public void testWKB2() throws Exception { } + @Test + public void testWKB3() throws Exception { + String multiPointWKT = "MULTIPOINT ZM(10 40 1 23, 40 30 2 45)"; + OGCGeometry geometry = OGCGeometry.fromText(multiPointWKT); + ByteBuffer byteBuffer = geometry.asBinary(); + OGCGeometry geomFromBinary = OGCGeometry.fromBinary(byteBuffer); + assertTrue(geometry.Equals(geomFromBinary)); + } } From c0c621789c4e2b4f8b11ef1b8dd032f207294857 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 21 Sep 2020 13:27:20 -0700 Subject: [PATCH 104/116] Geometry release v2.2.4 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 401315df..5a1468c6 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.3 + 2.2.4 ``` diff --git a/pom.xml b/pom.xml index a3dcbc19..34e8318d 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.3 + 2.2.4 jar Esri Geometry API for Java From a1af6612f4de7fc1baee1c331c335f154a4a96c9 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 8 Oct 2020 12:23:32 -0700 Subject: [PATCH 105/116] Fix exception in WKB export for polygon with first ring of zero area (#276) --- .../com/esri/core/geometry/MultiPathImpl.java | 43 +++++++++++-------- .../esri/core/geometry/TestImportExport.java | 23 ++++++---- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index c9400ee0..d70e5c5d 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -2133,27 +2133,34 @@ int getOGCPolygonCount() { protected void _updateOGCFlags() { if (_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) { _updateRingAreas2D(); - - int pathCount = getPathCount(); - if (pathCount > 0 && (m_pathFlags == null || m_pathFlags.size() < pathCount)) - m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(pathCount + 1); - - int firstSign = 1; - for (int ipath = 0; ipath < pathCount; ipath++) { - double area = m_cachedRingAreas2D.read(ipath); - if (ipath == 0) - firstSign = area > 0 ? 1 : -1; - if (area * firstSign > 0.0) - m_pathFlags.setBits(ipath, - (byte) PathFlags.enumOGCStartPolygon); - else - m_pathFlags.clearBits(ipath, - (byte) PathFlags.enumOGCStartPolygon); - } + _updateOGCFlagsHelper(); _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); } } + + private void _updateOGCFlagsHelper() { + int pathCount = getPathCount(); + if (pathCount > 0 && (m_pathFlags == null || m_pathFlags.size() < pathCount)) + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(pathCount + 1); + + // firstSign is the sign of first ring. + // a first ring with non zero area defines the + // value. First zero area rings are written out as enumOGCStartPolygon. + int firstSign = 0; + for (int ipath = 0; ipath < pathCount; ipath++) { + double area = m_cachedRingAreas2D.read(ipath); + if (firstSign == 0) { + // if the first ring is inverted we assume that the + // whole polygon is inverted. + firstSign = MathUtils.sign(area); + } + + if (area * firstSign > 0.0 || firstSign == 0) + m_pathFlags.setBits(ipath, (byte) PathFlags.enumOGCStartPolygon); + else + m_pathFlags.clearBits(ipath, (byte) PathFlags.enumOGCStartPolygon); + } + } public int getPathIndexFromPointIndex(int pointIndex) { int positionHint = m_currentPathIndex;// in case of multithreading diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 0b9e2bc8..73faed01 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -44,15 +44,6 @@ protected void tearDown() throws Exception { @Test public static void testImportExportShapePolygon() { -// { -// String s = "MULTIPOLYGON (((-1.4337158203098852 53.42590083930004, -1.4346462383651897 53.42590083930004, -1.4349713164114632 53.42426406667512, -1.4344808816770183 53.42391134176576, -1.4337158203098852 53.424339319373516, -1.4337158203098852 53.42590083930004, -1.4282226562499147 53.42590083930004, -1.4282226562499147 53.42262754610009, -1.423659941537096 53.42262754610009, -1.4227294921872726 53.42418897437618, -1.4199829101572732 53.42265258737483, -1.4172363281222147 53.42418897437334, -1.4144897460898278 53.42265258737625, -1.4144897460898278 53.42099079900008, -1.4117431640598568 53.42099079712516, -1.4117431640598568 53.41849780932388, -1.4112778948070286 53.41771711805022, -1.4114404909237805 53.41689867267529, -1.411277890108579 53.416080187950215, -1.4117431640598568 53.4152995338453, -1.4117431657531654 53.40953184824072, -1.41723632610001 53.40953184402311, -1.4172363281199125 53.406257299700044, -1.4227294921899158 53.406257299700044, -1.4227294921899158 53.40789459668797, -1.4254760767598498 53.40789460061099, -1.4262193642339867 53.40914148401417, -1.4273828468095076 53.409531853100034, -1.4337158203098852 53.409531790075235, -1.4337158203098852 53.41280609140024, -1.4392089843723568 53.41280609140024, -1.439208984371362 53.41608014067522, -1.441160015802268 53.41935368587538, -1.4427511170075604 53.41935368587538, -1.4447021484373863 53.42099064750012, -1.4501953124999432 53.42099064750012, -1.4501953124999432 53.43214683850347, -1.4513643355446106 53.434108816701794, -1.4502702625278232 53.43636597733034, -1.4494587195580948 53.437354845300334, -1.4431075935937656 53.437354845300334, -1.4372459179209045 53.43244635455021, -1.433996276212838 53.42917388040006, -1.4337158203098852 53.42917388040006, -1.4337158203098852 53.42590083930004)))"; -// Geometry g = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); -// boolean result1 = OperatorSimplify.local().isSimpleAsFeature(g, null, null); -// boolean result2 = OperatorSimplifyOGC.local().isSimpleOGC(g, null, true, null, null); -// Geometry simple = OperatorSimplifyOGC.local().execute(g, null, true, null); -// OperatorFactoryLocal.saveToWKTFileDbg("c:/temp/simplifiedeeee", simple, null); -// int i = 0; -// } OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); @@ -1754,6 +1745,20 @@ public static void testImportGeoJsonSpatialReference() throws Exception { assertTrue(mapGeometry4326.getGeometry().equals(mapGeometry3857.getGeometry())); } + @Test + public void testZeroRingWkb() { + //https://github.com/Esri/geometry-api-java/issues/275 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(0, 10); + poly.addEnvelope(new Envelope(1, 1, 3, 3), false); + + ByteBuffer bb = OperatorExportToWkb.local().execute(0, poly, null); + Geometry res = OperatorImportFromWkb.local().execute(0, Geometry.Type.Unknown, bb, null); + assertTrue(res.equals(poly)); + } + public static Polygon makePolygon() { Polygon poly = new Polygon(); poly.startPath(0, 0); From 48149c80c6cb9ff0f1575fcbb60d3cb942a80f87 Mon Sep 17 00:00:00 2001 From: sllynn Date: Mon, 4 Apr 2022 15:45:46 +0100 Subject: [PATCH 106/116] added spatial reference into calls to geojson operator for OGCMultiLineString and OGCMultiPolygon --- .../core/geometry/ogc/OGCMultiLineString.java | 2 +- .../core/geometry/ogc/OGCMultiPolygon.java | 2 +- .../esri/core/geometry/TestGeomToGeoJson.java | 62 +++++++++++++++++-- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 6a2381e3..e0608f61 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -64,7 +64,7 @@ public String asText() { public String asGeoJson() { OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.ExportToGeoJson); - return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, esriSR, getEsriGeometry()); } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 52afbe86..941bc7c2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -71,7 +71,7 @@ public ByteBuffer asBinary() { public String asGeoJson() { OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, esriSR, getEsriGeometry()); } @Override public int numGeometries() { diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index aa480b26..83271d5b 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -22,14 +22,9 @@ package com.esri.core.geometry; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPoint; -import com.esri.core.geometry.ogc.OGCMultiPoint; -import com.esri.core.geometry.ogc.OGCLineString; -import com.esri.core.geometry.ogc.OGCPolygon; +import com.esri.core.geometry.ogc.*; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import junit.framework.TestCase; import org.junit.Test; @@ -161,6 +156,20 @@ public void testOGCLineString() { assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]],\"crs\":null}", result); } + @Test + public void testOGCMultiLineStringCRS() throws IOException { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + + OGCMultiLineString multiLineString = new OGCMultiLineString(p, SpatialReference.create(4326)); + + String result = multiLineString.asGeoJson(); + assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", result); + } + @Test public void testPolygon() { Polygon p = new Polygon(); @@ -241,6 +250,24 @@ public void testMultiPolygon() throws IOException { assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]]}", result); } + @Test + public void testOGCMultiPolygonCRS() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + + String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + + JsonParser parser = jsonFactory.createParser(esriJsonPolygon); + MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); + + parsedPoly.setSpatialReference(SpatialReference.create(4326)); + Polygon poly = (Polygon) parsedPoly.getGeometry(); + OGCMultiPolygon multiPolygon = new OGCMultiPolygon(poly, parsedPoly.getSpatialReference()); + + + String result = multiPolygon.asGeoJson(); + assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", result); + } + @Test public void testEmptyPolygon() { @@ -334,6 +361,29 @@ public void testOGCPolygonWithHole() { assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":null}", result); } + @Test + public void testOGCPolygonWithHoleCRS() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + SpatialReference sr = SpatialReference.create(4326); + + OGCPolygon ogcPolygon = new OGCPolygon(p, sr); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", result); + } + @Test public void testGeometryCollection() { SpatialReference sr = SpatialReference.create(4326); From 74a99e7d2027a80c995679afea78de4bfe432569 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 31 May 2022 09:28:00 -0700 Subject: [PATCH 107/116] Remove a copyright comment (#293) --- .../com/esri/core/geometry/JsonReaderCursor.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/JsonReaderCursor.java b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java index 94f72a30..8e0f16de 100644 --- a/src/main/java/com/esri/core/geometry/JsonReaderCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java @@ -19,21 +19,6 @@ 380 New York Street Redlands, California, USA 92373 - email: contracts@esri.com - */ -/* - COPYRIGHT 1995-2017 ESRI - - TRADE SECRETS: ESRI PROPRIETARY AND CONFIDENTIAL - Unpublished material - all rights reserved under the - Copyright Laws of the United States. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - email: contracts@esri.com */ From 60f239c0515ab43f6354f38ce8728b8656cb9277 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Thu, 2 Jun 2022 08:37:38 -0700 Subject: [PATCH 108/116] Link Esri.github.io for Javadoc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a1468c6..d64e0db5 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: ## Documentation * [geometry-api-java/Wiki](https://github.com/Esri/geometry-api-java/wiki/) -* [geometry-api-java/Javadoc](http://esri.github.com/geometry-api-java/javadoc/) +* [geometry-api-java/Javadoc](http://Esri.github.io/geometry-api-java/javadoc/) ## Resources From 20c5cbd814c3cdedbcda81ee1086d3888ba6bfd5 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Thu, 2 Feb 2023 14:38:25 -0800 Subject: [PATCH 109/116] Temp file for testing geometry server --- data/AK_3338_MULTIPOINTS_JSON_GEOMETRY.TXT | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/AK_3338_MULTIPOINTS_JSON_GEOMETRY.TXT diff --git a/data/AK_3338_MULTIPOINTS_JSON_GEOMETRY.TXT b/data/AK_3338_MULTIPOINTS_JSON_GEOMETRY.TXT new file mode 100644 index 00000000..b5b56aa2 --- /dev/null +++ b/data/AK_3338_MULTIPOINTS_JSON_GEOMETRY.TXT @@ -0,0 +1 @@ +{"geometryType" : "esriGeometryMultipoint","geometries" : [{"points" : [[-422460.131402843,994009.192353492],[-425621.226482613,991032.590063632],[-424201.949054824,986531.313422172],[-449572.721437023,976682.216676533],[-445757.973353393,983081.453278027],[-447565.625031025,986731.192475301],[-449041.127659045,981860.757795434],[-471763.09284187,990302.643172824],[-455639.101461489,988686.731761123],[-440188.279632496,1003500.47178871],[-446996.90483744,1002326.25256318],[-445579.971866621,1023106.39454493],[-448587.475766268,1030185.56721526],[-431199.893662673,1036568.02753379],[-447336.100526733,1039637.52849332],[-448745.674369452,1033832.34834033]]}],"spatialReference" : {"wkid" : 3338}} \ No newline at end of file From db56dc80057192be78478ae3fba23b6f6026113b Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Thu, 2 Feb 2023 14:50:08 -0800 Subject: [PATCH 110/116] Temp file for server test --- data/INTERSTATE10_102009_POLYLINE_GEOMETRY2.TXT | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/INTERSTATE10_102009_POLYLINE_GEOMETRY2.TXT diff --git a/data/INTERSTATE10_102009_POLYLINE_GEOMETRY2.TXT b/data/INTERSTATE10_102009_POLYLINE_GEOMETRY2.TXT new file mode 100644 index 00000000..9b46512f --- /dev/null +++ b/data/INTERSTATE10_102009_POLYLINE_GEOMETRY2.TXT @@ -0,0 +1 @@ +[{"paths" : [[[-1860932.82127223,-388270.616031549],[-1846957.20718179,-391464.562129146],[-1840307.63567473,-394033.481586339],[-1918707.73887222,-377413.212554243],[-1914222.88941537,-376015.003856398],[-1906334.46244651,-378208.210897945],[-1898644.6777357,-378779.474294907],[-1891889.43366789,-379905.354171953],[-1884733.69891171,-382651.410161352],[-1883937.29591364,-382454.447279954],[-1936756.2564541,-373739.523949398],[-1924276.48838184,-376332.106982319],[-1920512.79000794,-377655.198817429],[-1918707.73887222,-377413.212554243],[-1883937.29591364,-382454.447279954],[-1870439.63374804,-384750.380696851],[-1867879.05963065,-386707.364069535],[-1860932.82127223,-388270.616031549],[-1942584.19056967,-372494.956589206],[-1936756.2564541,-373739.523949398],[-1409097.63488798,-551869.261070279],[-1409523.01440632,-554656.915811257],[-1840307.63567473,-394033.481586339],[-1831508.27455814,-398407.122999975],[-1826863.67560942,-402643.014773565],[-1821015.04269462,-406241.500127598],[-1817735.66345434,-412991.69676903],[-1813480.00041146,-415363.587122058],[-1804394.9196369,-416954.436337427],[-1800845.07258485,-419152.320953585],[-1791834.05687365,-420232.283109151],[-1784027.25683367,-423374.413978621],[-1779380.26258846,-426183.936541837],[-1777557.80520672,-428104.598329479],[-1771046.93481117,-436333.194981073],[-1765596.1415797,-442582.458365353],[-1760884.64466077,-445947.366036213],[-1757733.87097148,-447947.089025783],[-1754170.7352927,-452948.196724181],[-1747551.58921333,-456877.696319367],[-1745156.79401807,-456873.145387547],[-1740963.5553046,-459914.071569563],[-1721825.23950976,-466900.622601992],[-1705392.42558765,-469042.810066407],[-1686982.69670194,-469009.666799931],[-1684122.55796452,-469755.631053038],[-1675999.9086547,-473454.5971056],[-1653558.62949401,-487961.62076816],[-1627004.40554155,-493383.327896274],[-1619637.12729604,-495073.914894848],[-1617155.56334704,-495351.793322275],[-1616112.99057719,-495282.072620393],[-1605499.7571852,-494572.633205635],[-1602626.47117254,-493620.772230954],[-1590641.82987419,-495281.949210788],[-1588314.58186174,-496080.371608566],[-1586759.81276419,-496468.345673322],[-1583113.44679489,-495731.506909874],[-1575775.215391,-496394.444746467],[-1571678.82445026,-498818.807671611],[-1480398.4816489,-532854.62017299],[-1452852.22751085,-547262.639157195],[-1444216.68445375,-544966.879608767],[-1409097.63488798,-551869.261070279],[-1409523.01440632,-554656.915811257],[-1404176.29800062,-556880.553833265],[-1399534.3089503,-559866.27553626],[-1401783.49287842,-571710.278685015],[-1393513.70189299,-591417.565729961],[-1388075.76799737,-604306.93702367],[-1384196.66113984,-613280.686797792],[-1384815.79170956,-618950.906859659],[-1386132.48063215,-625339.350743451],[-1386132.48063215,-625339.350743451],[-1373313.84971144,-638740.589542896],[-1361965.20235478,-651191.082814489],[-1355218.50835855,-664881.949750467],[-1347001.22496875,-674725.006126551],[-1340260.56839497,-684926.857390148],[-1334998.33589704,-696799.859699966],[-1334665.42360899,-698162.030945824],[-1335844.30363362,-702694.148342999],[-1245210.95724443,-718431.080285172],[-1238717.2640838,-713547.342478722],[-1230199.08023375,-706191.966182859],[-1224070.47756294,-702312.559825601],[-1218105.24137078,-702436.57083677],[-1214061.44067799,-702949.590340844],[-1209021.15588085,-706281.9252118],[-1203124.24661422,-708821.565988461],[-1201075.35462315,-710198.06636224],[-1198540.32612232,-710628.525608014],[-1183330.43394871,-716802.70081487],[-1181419.54410298,-718449.355276614],[-1175919.76505775,-721024.065253884],[-1173663.57075243,-721023.633571428],[-1167076.61455832,-724198.542337905],[-1164285.63224225,-726910.514437306],[-1163352.53966945,-727817.234271247],[-1159099.8246554,-727921.741917781],[-1154948.4644574,-726408.995383046],[-1139421.30728818,-719369.434615407],[-1124845.23519314,-722912.914602716],[-1106243.39289231,-739872.893279173],[-1104217.10771599,-740758.388433313],[-1097691.72532186,-740422.708079669],[-1047775.54466794,-737914.788753216],[-1035923.5080803,-738027.748654808],[-1015627.39806565,-745993.662454439],[-1012803.9856812,-745150.854690653],[-997372.532029047,-747245.187918835],[-971736.184680474,-746673.101628281],[-961364.955127864,-752137.603723688],[-957982.669061466,-753802.10402492],[-955359.597173141,-754966.139811466],[-1335844.30363362,-702694.148342999],[-1334518.25884188,-703333.677362167],[-1330862.04433071,-705104.54484834],[-1328701.96846369,-708008.476326118],[-1327769.68784457,-710920.085437616],[-1311621.8901602,-726109.485508868],[-1306081.06552574,-728409.866473137],[-1301017.41111561,-729324.710614419],[-1293254.84575945,-732836.804983335],[-1283715.20817654,-734332.440884748],[-1280390.19280606,-733767.230085431],[-1277015.36942673,-733841.097825953],[-1275645.35862002,-735166.906876661],[-1262040.57789063,-732172.040312738],[-1253875.6884213,-725875.517154103],[-1248666.43278994,-720299.256004177],[-1245210.95724443,-718431.080285172],[-955359.597173141,-754966.139811466],[-952056.701432983,-763124.339787109],[-948546.873621568,-774860.547430418],[-948352.879021394,-779844.258199058],[-947981.528764897,-784911.407359661],[-948659.483767897,-790266.338447856],[-947649.70242825,-797238.392850766],[-946341.462502304,-798533.006572085],[-945874.23940249,-800511.147804896],[-942614.750164408,-803698.174722712],[-940056.530035286,-803611.400076444],[-937463.330307175,-803266.045082759],[-933617.842662257,-804094.590924077],[-931177.065105375,-805995.30588653],[-922450.756370351,-817038.699657056],[-912259.387590314,-837019.469569108],[-901695.153574011,-847078.644808715],[-896571.22498808,-850482.87667744],[-892978.23510254,-856268.93524594],[-886226.45560338,-863040.083826403],[-886156.02290211,-867389.174209029],[-880788.106964542,-873704.139337095],[-876152.801603457,-876262.74407912],[-872750.915412895,-876293.58252762],[-867948.585353543,-874609.483239542],[-864204.78280361,-875575.372986374],[-859352.212917072,-874810.997216439],[-853482.951382145,-877962.665940733],[-843216.321624105,-881677.853694242],[-816012.209487552,-893794.042653468],[-812378.845600151,-896009.064203036],[-810202.945501474,-896264.755720976],[-805510.700365156,-898678.445928985],[-801719.872894014,-898725.987726727],[-780450.130108813,-899368.06735481],[-764889.739533328,-898413.060026565],[-751607.914276247,-901208.140214766],[-742971.489015314,-901593.805686476],[-738658.90076313,-901906.344584241],[-736743.357981191,-900552.363631087],[-732696.174564229,-900320.440779244],[-730973.339967641,-898908.754305918],[575352.916860983,-994799.763036069],[583180.696877742,-993876.639834322],[585635.319253531,-993021.122789163],[587788.744863143,-992270.228826663],[591580.042340177,-990821.663686742],[602683.205299627,-988729.490384552],[613752.039933598,-983579.467164824],[633884.082196908,-977117.455852276],[640318.1819108,-975177.757418059],[652905.673247639,-973405.41363682],[661345.941279076,-972867.572927195],[668348.050774387,-973309.256311963],[677951.283096452,-975254.718438393],[684549.086372028,-974852.51671385],[693748.799987452,-967602.526018881],[695146.656861169,-966542.25086226],[697943.398428829,-964421.275341788],[715668.230698317,-954584.52750052],[718041.51656543,-949892.762486286],[720361.063979655,-948358.383244131],[720361.063979655,-948358.383244131],[722461.933493544,-946500.998072774],[725026.372146054,-943056.777194635],[725356.402677451,-941625.351253375],[726144.515536742,-940450.241305277],[729054.978127155,-940752.466844363],[729906.557033605,-941362.396286676],[730647.198649882,-941998.273539696],[731992.86996737,-943892.253970391],[734695.53116178,-944503.550751599],[738247.19837036,-943892.056617691],[739682.309298133,-943540.046598061],[751175.404405563,-942808.223649276],[751905.629882262,-942800.904792179],[757321.457602038,-942871.80730766],[766809.053722063,-944519.369922698],[775381.973460679,-948930.909534568],[785407.385115877,-947426.713959655],[785599.613640667,-947397.767624351],[788962.653876163,-948507.547665137],[793306.344710799,-951692.918358162],[793997.067352187,-952146.647076597],[798073.787655093,-954730.838563901],[800495.405708707,-954929.43422266],[802431.901650757,-954747.205541846],[804741.933715814,-954423.026444915],[808298.498089107,-953462.469815359],[811745.508145724,-950678.148756826],[817512.292610224,-948658.962792098],[819519.381338945,-944570.135595441],[821440.198836967,-943075.05852926],[827174.717361574,-940245.448834636],[839385.62671965,-933086.518392322],[850281.998571202,-928032.35908958],[860971.372316781,-925387.830717164],[864680.720703888,-924845.595124155],[872523.531373039,-926144.045883601],[877406.707821227,-924924.744105547],[883255.913009189,-923053.285452875],[899710.637324936,-923996.41378933],[901447.685004908,-923397.479000235],[918336.586850844,-921411.678442947],[922536.461117099,-920515.947861362],[932244.95164992,-915400.30711948],[940974.555191437,-913128.488731009],[953119.262931405,-911173.475732888],[968591.969932034,-908813.755972633],[975149.423808406,-908408.646619507],[981131.620632017,-910117.842853021],[986746.9320397,-911628.210819928],[991676.755125067,-913124.212744749],[997869.880795841,-916561.209290884],[1003303.29037402,-916972.984639167],[1009707.97704788,-916097.084768555],[1024695.33934841,-916174.207532096],[1028115.45263723,-916926.983445833],[1032978.67973518,-919891.998800418],[1035343.79026678,-920822.265641539],[1041521.59196371,-922506.218808777],[1045393.20074409,-923740.057209495],[1054904.39396888,-923457.029418946],[1064213.54590749,-924319.217929752],[1067432.55659644,-924218.388176459],[1073629.10137284,-924046.20778549],[1076666.8357316,-924064.856544828],[1081289.82014802,-924230.771361769],[1095185.07046038,-921749.534865562],[1103957.59147264,-921406.005526682],[1107546.54135469,-921941.201116782],[1124273.1703054,-923351.235853165],[1131491.36685625,-923147.365522488],[1139748.46585969,-920571.297893148],[1147330.93847969,-920516.587978908],[1157640.55723499,-923149.834098207],[1171713.02990369,-926831.518180989],[1182881.97724001,-925307.199021827],[1188554.90125587,-923240.363419346],[1193689.97457051,-922801.368888602],[1199206.83152608,-923827.805681958],[1208292.66652335,-928024.891631578],[1208292.66652335,-928024.891631578],[1216069.59391038,-929113.119365303],[1219674.33773366,-928569.094348959],[1223845.85844784,-928638.569319719],[1231791.62892553,-927117.143494849],[1236936.54454001,-927113.837907763],[1250540.82051822,-923473.029772133],[1254714.25651926,-924761.506069283],[1258038.38732707,-924690.498666897],[1269770.52199636,-918593.472029025],[1270880.32317345,-918064.874362793],[1282803.18836572,-914677.093761606],[1301672.21428071,-909640.834250306],[1305278.85905396,-907921.688813134],[1308848.19800415,-907476.307131558],[513165.371872435,-1023903.77019165],[506949.089732182,-1019596.84849971],[487004.816992026,-1018196.43208866],[480396.822018087,-1018487.39495938],[475683.512661196,-1018687.58626175],[471032.443579414,-1017084.40854554],[468387.600478024,-1016257.65520298],[466656.977378729,-1014586.19051933],[454684.734944637,-998216.765140984],[451912.800164073,-994972.065766407],[449351.585885809,-993265.855296364],[575352.916860983,-994799.763036069],[574886.966388113,-997700.638645804],[574721.887806386,-999413.371840635],[574191.255501014,-1004069.01729427],[572274.323517449,-1008439.88196554],[570390.649642987,-1009989.55795832],[566487.00624464,-1011252.1035564],[564557.227584298,-1014798.51858814],[559457.956022949,-1022277.00001518],[553184.517089836,-1026439.27334736],[552319.209393629,-1027914.1568042],[551148.300739445,-1029217.27749673],[549693.345567865,-1029325.16638881],[543382.630840925,-1029695.53597659],[539462.203590489,-1030083.17584039],[539462.203590489,-1030083.17584039],[535721.038576166,-1030262.83373266],[525175.774427311,-1028756.12205238],[515725.28293541,-1025562.88539953],[513165.371872435,-1023903.77019165],[449351.585885809,-993265.855296364],[442449.924303394,-990857.489711945],[439623.61408176,-990190.004546803],[390286.240668772,-997903.671773396],[385832.437516809,-1000993.57608467],[383570.456511053,-1004070.91609958],[369142.13795887,-1012108.12841001],[367061.095067366,-1012307.09352576],[364776.099269007,-1013055.0970963],[362507.399315924,-1013364.53363339],[354569.927435781,-1014131.24261668],[343696.660703846,-1014033.07168194],[332859.277135792,-1016519.10557814],[318885.62701576,-1017372.74945521],[311882.180644164,-1016904.35802189],[307302.971576565,-1015582.29413376],[282987.134808647,-1016299.25836854],[277591.613696029,-1016489.41113601],[264468.841446095,-1017666.65552109],[259872.978577373,-1018139.01653873],[256488.965649622,-1019204.8451938],[251490.940512464,-1019511.60677846],[249879.987365968,-1020840.72521134],[241865.025376377,-1021894.76929824],[231353.805569256,-1023366.86074435],[224966.301890999,-1025277.0027521],[219223.287156696,-1028355.99422714],[217667.54080258,-1029097.70230575],[212200.444493473,-1031994.1412628],[211954.602217841,-1032133.24034698],[210358.870227183,-1033036.7451334],[207774.282372493,-1033176.61064378],[203341.460580485,-1034504.35588428],[201039.029443652,-1034897.87590271],[195447.163130772,-1033606.92314442],[182218.186988688,-1033465.75818353],[179765.51182488,-1033949.74087309],[176002.677693372,-1036759.26769463],[172488.873477125,-1037006.27198673],[171281.908600622,-1037977.11157945],[170651.178378547,-1040330.24596238],[170802.730402945,-1041404.14984348],[161766.771268855,-1051819.97767216],[148523.545624947,-1065131.6007235],[119780.164359756,-1064594.69196167],[101896.32205977,-1066293.05961961],[97575.3897995831,-1068400.130909],[87164.7810864157,-1069746.29753127],[78784.3400300031,-1073168.67144331],[67330.0926725612,-1072355.12042547],[60429.7761875881,-1073236.29488827],[59500.7512043746,-1073343.73598084],[57222.8460555218,-1073103.48513836],[57222.8460555218,-1073103.48513836],[49830.8268393272,-1072942.29953355],[32240.2019934744,-1072209.77086171],[21788.576477514,-1071870.075084],[12126.5746239013,-1073604.45102842],[1786.24835324287,-1072927.07970355],[-5322.72282712883,-1074135.17138073],[-10451.2225478069,-1073384.08689046],[-14808.2158731411,-1076263.43642263],[-16316.716143104,-1076248.38328289],[-18832.3370748974,-1074732.09912827],[-23621.0126606509,-1075161.5112425],[-39474.7653784111,-1079352.49162442],[-45820.980666855,-1079763.72494873],[-50551.3845846837,-1082237.45755732],[-73206.9778210527,-1081513.02986402],[-77452.2100060822,-1080601.5406818],[-80074.7425430997,-1079321.66002543],[-83727.4270109524,-1079359.28943472],[-102716.633047599,-1079417.78356824],[-113039.021397429,-1079458.21945742],[-115588.807872392,-1081001.73007724],[-120824.321561725,-1082720.96050199],[-131208.440888363,-1084168.00124346],[-142058.250604208,-1083988.06839615],[-149209.512565563,-1083870.98809668],[-153718.477661156,-1084550.16344772],[-163319.881304415,-1085882.01155002],[-166662.819585015,-1087730.93698476],[-172370.484291913,-1089086.64234129],[-178612.029413752,-1089299.41957619],[-181042.082298536,-1089192.7556272],[-182588.888330004,-1088930.0297339],[-185612.836642543,-1090159.71734169],[-187630.148920998,-1092845.42273789],[-222338.217162394,-1105774.19645384],[-222338.217162394,-1105774.19645384],[-226199.731674855,-1108282.10690453],[-229675.316578143,-1109784.31512831],[-234306.079537844,-1109870.6101174],[-233264.613641968,-1108357.80091609],[-233696.504949821,-1104928.44643169],[-233803.705371492,-1104046.76811036],[-234313.021256047,-1100176.42424255],[-236955.53945654,-1097185.46393511],[-239908.560746955,-1092428.60263746],[-245109.634624337,-1078035.27214725],[-249294.186375222,-1071428.20977862],[-254911.220796075,-1067481.51331602],[-255046.054732099,-1063975.01513734],[-259235.908987459,-1057517.38243586],[-264287.710564967,-1051848.92930436],[-267665.532240482,-1045770.84704077],[-279560.028027375,-1037515.59035431],[-286285.477524195,-1035562.24714744],[-292905.193336488,-1032351.52127157],[-301698.666699814,-1024480.54636189],[-311865.133538458,-1020090.61775317],[-323594.327628477,-1011555.37551476],[-326322.783374792,-1009491.40029984],[-331711.161221129,-1000665.12628752],[-336081.089040626,-997267.24886101],[-339475.470812427,-995949.318787587],[-339958.786865015,-994812.722555545],[-340865.741054511,-992300.005228251],[-342509.488854784,-988881.91340958],[-345325.387696331,-986621.845757984],[-348354.903717,-984788.098556224],[-353370.600026566,-983832.189732435],[-366677.9065106,-989713.442403571],[-381308.67697851,-993644.278984853],[-386048.58799054,-993038.102078345],[-394020.240594272,-989714.314681328],[-404559.96427707,-984091.222154755],[-410826.064758872,-979683.638887757],[-415891.238631707,-977704.971530676],[-423719.290385192,-974483.766951312],[-431248.784620829,-973213.152785218],[-437122.904076817,-970857.022997443],[-444060.088484067,-969419.275002957],[-447234.756349965,-966559.733401117],[-449860.246339521,-966533.504819414],[-470546.939463806,-959560.82603034],[-474825.983933445,-960569.365286018],[-480100.600879141,-962063.456826469],[-487708.878630979,-962478.905617731],[-492691.549147872,-961904.590094888],[-508185.848272988,-957298.387205547],[-511687.555642549,-955244.498898082],[-520911.221610588,-954854.489576609],[-531873.66849203,-953263.135724329],[-539959.056363882,-950364.808982733],[-544112.872928349,-945666.384008732],[-548415.808178655,-941057.117572303],[-553646.204229286,-937518.545649007],[-561158.748486031,-933963.897264104],[-569210.143925863,-932511.361288993],[-597091.638105678,-934792.618584339],[-604779.108251832,-932825.646069359],[-615240.002974165,-929898.073583769],[-626111.033747536,-929417.402304793],[-626882.54912018,-929282.989989699],[-627960.517261167,-929414.874960798],[-632640.499376021,-928663.184896687],[-641805.443349296,-927102.68493463],[-654533.318799192,-923818.890523293],[-691295.485187774,-913786.75898745],[-696149.868873251,-912568.82998114],[-700803.719446554,-911333.63401215],[-722093.029572382,-905199.219738293],[-726656.559593404,-901105.082166024],[-730973.339967641,-898908.754305918],[-229675.316578143,-1109784.31512831],[-230457.101281106,-1109847.02139603],[-232562.931887238,-1110094.44705976],[-234306.079537844,-1109870.6101174]]]}] \ No newline at end of file From 4816815401e17be4eea3a7969d472bfd231d3252 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Thu, 2 Feb 2023 14:56:16 -0800 Subject: [PATCH 111/116] Temp file for server test --- data/AreasAndLengths.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 data/AreasAndLengths.txt diff --git a/data/AreasAndLengths.txt b/data/AreasAndLengths.txt new file mode 100644 index 00000000..f58196f9 --- /dev/null +++ b/data/AreasAndLengths.txt @@ -0,0 +1,15 @@ +[ + { + "rings" : + [ + [[-117,34],[-116,34],[-117,33],[-117,34]], + [[-115,44],[-114,43],[-115,43],[-115,44]] + ] + }, + { + "rings" : + [ + [[32,17],[31,17],[30,17],[30,16],[32,17]] + ] + } +] \ No newline at end of file From e237fc0fe4a7fb43583dd335d58e1f23ab18ac4b Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Mon, 13 Feb 2023 19:50:36 +0100 Subject: [PATCH 112/116] Added OSGi package information (#300) --- pom.xml | 58 +++++++++++++++++-- .../esri/core/geometry/ogc/package-info.java | 19 ++++++ .../com/esri/core/geometry/package-info.java | 19 ++++++ 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/ogc/package-info.java create mode 100644 src/main/java/com/esri/core/geometry/package-info.java diff --git a/pom.xml b/pom.xml index 34e8318d..11b47dea 100755 --- a/pom.xml +++ b/pom.xml @@ -101,14 +101,23 @@ 2.9.6 4.12 0.9 + 7.0.0 2.3.1 2.2.1 3.0.0-M1 + 3.3.0 + 6.4.0 + + org.osgi + osgi.annotation + ${osgi.core.version} + provided + com.fasterxml.jackson.core jackson-core @@ -151,12 +160,49 @@ - maven-compiler-plugin - ${compiler.plugin.version} - - ${java.source.version} - ${java.target.version} - + maven-compiler-plugin + ${compiler.plugin.version} + + ${java.source.version} + ${java.target.version} + + + + biz.aQute.bnd + bnd-maven-plugin + ${bnd.version} + + + + bnd-process + + + + + + bnd.bnd + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${jar.plugin.version} + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + true + org.apache.maven.plugins diff --git a/src/main/java/com/esri/core/geometry/ogc/package-info.java b/src/main/java/com/esri/core/geometry/ogc/package-info.java new file mode 100644 index 00000000..b6b69aa6 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/ogc/package-info.java @@ -0,0 +1,19 @@ +/* + Copyright 2017-2023 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +@org.osgi.annotation.bundle.Export +@org.osgi.annotation.versioning.Version("2.2.4") +package com.esri.core.geometry.ogc; diff --git a/src/main/java/com/esri/core/geometry/package-info.java b/src/main/java/com/esri/core/geometry/package-info.java new file mode 100644 index 00000000..5a613f65 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/package-info.java @@ -0,0 +1,19 @@ +/* + Copyright 2017-2023 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +@org.osgi.annotation.bundle.Export +@org.osgi.annotation.versioning.Version("2.2.4") +package com.esri.core.geometry; From d9ed3598b72029c9ebde024e0e616933cff81db2 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Sun, 3 Sep 2023 19:20:56 +0200 Subject: [PATCH 113/116] Declare "com.esri.geometry.api" as Java Module Name. (#303) Also set version number to 2.2.5-SNAPSHOT on the assumption that other changes may be added before release. --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11b47dea..472eb349 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.4 + 2.2.5-SNAPSHOT jar Esri Geometry API for Java @@ -179,6 +179,7 @@ Date: Sun, 5 Nov 2023 23:17:12 -0400 Subject: [PATCH 114/116] New test case added --- pom.xml | 6 ++ .../esri/core/geometry/TestEnvelope1D.java | 22 ++++++ .../core/geometry/TestIndexHashTable.java | 72 +++++++++++++++++++ .../com/esri/core/geometry/TestJSONUtils.java | 47 ++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/test/java/com/esri/core/geometry/TestEnvelope1D.java create mode 100644 src/test/java/com/esri/core/geometry/TestIndexHashTable.java create mode 100644 src/test/java/com/esri/core/geometry/TestJSONUtils.java diff --git a/pom.xml b/pom.xml index 472eb349..b59ca4a4 100755 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,12 @@ ${junit.version} test + + org.mockito + mockito-core + 3.12.4 + test + org.openjdk.jol jol-core diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope1D.java b/src/test/java/com/esri/core/geometry/TestEnvelope1D.java new file mode 100644 index 00000000..4ecd8cbc --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestEnvelope1D.java @@ -0,0 +1,22 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; +public class TestEnvelope1D extends TestCase{ + @Test + public void testCalculateToleranceFromEnvelopeEmpty() { + Envelope1D envelope = new Envelope1D(); + envelope.setEmpty(); + double tolerance = envelope._calculateToleranceFromEnvelope(); + assertEquals(100.0 * NumberUtils.doubleEps(), tolerance, 0.0001); + } + + @Test + public void testCalculateToleranceFromEnvelopeNonEmpty() { + Envelope1D envelope = new Envelope1D(2.0, 4.0); + double tolerance = envelope._calculateToleranceFromEnvelope(); + assertEquals(2.220446049250313e-14, tolerance, 1e-10); + } + + +} diff --git a/src/test/java/com/esri/core/geometry/TestIndexHashTable.java b/src/test/java/com/esri/core/geometry/TestIndexHashTable.java new file mode 100644 index 00000000..36ec75ac --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestIndexHashTable.java @@ -0,0 +1,72 @@ +package com.esri.core.geometry; +import org.junit.Test; +import junit.framework.TestCase; +public class TestIndexHashTable extends TestCase{ + @Test + public void testAddElement() { + IndexHashTable.HashFunction hashFunction = new IndexHashTable.HashFunction() { + @Override + public int getHash(int element) { + return element % 10; // A simple hash function for testing + } + + @Override + public boolean equal(int element1, int element2) { + return element1 == element2; + } + + @Override + public int getHash(Object elementDescriptor) { + return ((Integer) elementDescriptor) % 10; + } + + @Override + public boolean equal(Object elementDescriptor, int element) { + return ((Integer) elementDescriptor) == element; + } + }; + + IndexHashTable hashTable = new IndexHashTable(10, hashFunction); + + int element1 = 5; + + int node1 = hashTable.addElement(element1); + + assertEquals(node1, hashTable.findNode(element1)); + } + + @Test + public void testDeleteElement() { + IndexHashTable.HashFunction hashFunction = new IndexHashTable.HashFunction() { + @Override + public int getHash(int element) { + return element % 10; // A simple hash function for testing + } + + @Override + public boolean equal(int element1, int element2) { + return element1 == element2; + } + + @Override + public int getHash(Object elementDescriptor) { + return ((Integer) elementDescriptor) % 10; + } + + @Override + public boolean equal(Object elementDescriptor, int element) { + return ((Integer) elementDescriptor) == element; + } + }; + + IndexHashTable hashTable = new IndexHashTable(10, hashFunction); + + int element1 = 5; + + int node1 = hashTable.addElement(element1); + + hashTable.deleteElement(element1); + assertEquals(IndexHashTable.nullNode(), hashTable.findNode(element1)); + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestJSONUtils.java b/src/test/java/com/esri/core/geometry/TestJSONUtils.java new file mode 100644 index 00000000..86a29aff --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestJSONUtils.java @@ -0,0 +1,47 @@ +package com.esri.core.geometry; + +import org.junit.Test; +import junit.framework.TestCase; +import org.mockito.Mockito; +public class TestJSONUtils extends TestCase{ + + @Test + public void testReadDoubleWithFloatValue() { + JsonReader parser = Mockito.mock(JsonReader.class); + Mockito.when(parser.currentToken()).thenReturn(JsonReader.Token.VALUE_NUMBER_FLOAT); + Mockito.when(parser.currentDoubleValue()).thenReturn(3.14); + + double result = JSONUtils.readDouble(parser); + assertEquals(3.14, result, 0.0001); + } + + @Test + public void testReadDoubleWithIntValue() { + JsonReader parser = Mockito.mock(JsonReader.class); + Mockito.when(parser.currentToken()).thenReturn(JsonReader.Token.VALUE_NUMBER_INT); + Mockito.when(parser.currentIntValue()).thenReturn(42); + + double result = JSONUtils.readDouble(parser); + assertEquals(42.0, result, 0.0001); + } + + @Test + public void testReadDoubleWithNullValue() { + JsonReader parser = Mockito.mock(JsonReader.class); + Mockito.when(parser.currentToken()).thenReturn(JsonReader.Token.VALUE_NULL); + + double result = JSONUtils.readDouble(parser); + assertTrue(Double.isNaN(result)); + } + + @Test + public void testReadDoubleWithNaNString() { + JsonReader parser = Mockito.mock(JsonReader.class); + Mockito.when(parser.currentToken()).thenReturn(JsonReader.Token.VALUE_STRING); + Mockito.when(parser.currentString()).thenReturn("NaN"); + + double result = JSONUtils.readDouble(parser); + assertTrue(Double.isNaN(result)); + } + +} From bde380cb3305802f03114189a95a010e1ef93d60 Mon Sep 17 00:00:00 2001 From: "fabian.maysen" Date: Thu, 21 Mar 2024 12:17:21 -0300 Subject: [PATCH 115/116] make public the execute method in OperatorBuffer --- .../com/esri/core/geometry/OperatorBuffer.java | 2 +- .../com/esri/core/geometry/ogc/OGCGeometry.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java index 93c71b02..4150a9f9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorBuffer.java @@ -81,7 +81,7 @@ public abstract Geometry execute(Geometry inputGeometry, *Note that max_deviation can be exceeded because geometry is generalized with 0.25 * real_deviation, also input segments closer than 0.25 * real_deviation are *snapped to a point. */ - abstract GeometryCursor execute(GeometryCursor input_geometries, SpatialReference sr, double[] distances, double max_deviation, int max_vertices_in_full_circle, boolean b_union, ProgressTracker progress_tracker); + public abstract GeometryCursor execute(GeometryCursor input_geometries, SpatialReference sr, double[] distances, double max_deviation, int max_vertices_in_full_circle, boolean b_union, ProgressTracker progress_tracker); public static OperatorBuffer local() { return (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator( diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 17ef2f8f..fba49234 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -466,6 +466,21 @@ public OGCGeometry buffer(double distance) { return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); } + public OGCGeometry buffer(double distance, int max_vertices_in_full_circle) { + OperatorBuffer op = (OperatorBuffer) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Buffer); + if (distance == 0) {// when distance is 0, return self (maybe we should + // create a copy instead). + return this; + } + + double d[] = { distance }; + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), getEsriSpatialReference(), d, Double.NaN, max_vertices_in_full_circle, true, + null); + return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); + } + public OGCGeometry centroid() { OperatorCentroid2D op = (OperatorCentroid2D) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.Centroid2D); From 81eddaa4b6a3df6d0fb3898f2a2e7078e1073492 Mon Sep 17 00:00:00 2001 From: "fabian.maysen" Date: Tue, 16 Apr 2024 10:51:37 -0300 Subject: [PATCH 116/116] add max_deviation parameter in buffer method --- src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index fba49234..a6fdeaa9 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -466,7 +466,7 @@ public OGCGeometry buffer(double distance) { return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); } - public OGCGeometry buffer(double distance, int max_vertices_in_full_circle) { + public OGCGeometry buffer(double distance, int max_vertices_in_full_circle, double max_deviation) { OperatorBuffer op = (OperatorBuffer) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.Buffer); if (distance == 0) {// when distance is 0, return self (maybe we should @@ -476,7 +476,7 @@ public OGCGeometry buffer(double distance, int max_vertices_in_full_circle) { double d[] = { distance }; com.esri.core.geometry.GeometryCursor cursor = op.execute( - getEsriGeometryCursor(), getEsriSpatialReference(), d, Double.NaN, max_vertices_in_full_circle, true, + getEsriGeometryCursor(), getEsriSpatialReference(), d, max_deviation, max_vertices_in_full_circle, true, null); return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); }

?@Ib4$R$ZbA6MIsF(ZEk4Ii=sfRBa)&Yo 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�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 TY(h;@!?}i$7z!3V8^-w`q)kqw diff --git a/build.xml b/build.xml index 5cf6c1b8..a4bc51b1 100644 --- a/build.xml +++ b/build.xml @@ -14,10 +14,14 @@ - + + + - + + + diff --git a/pom.xml b/pom.xml index f9e62a0c..d55f9fcf 100755 --- a/pom.xml +++ b/pom.xml @@ -98,9 +98,9 @@ 1.6 - 2.6.5 + 2.9.4 4.12 - 0.2 + 0.9 2.3.1 @@ -115,14 +115,14 @@ ${jackson.version} false + + junit junit ${junit.version} test - - org.openjdk.jol jol-core diff --git a/src/main/java/com/esri/core/geometry/CombineOperator.java b/src/main/java/com/esri/core/geometry/CombineOperator.java index 43e5ac2c..e3d1bcc1 100644 --- a/src/main/java/com/esri/core/geometry/CombineOperator.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -36,7 +36,8 @@ public interface CombineOperator { * Operation on two geometries, returning a third. Examples include * Intersection, Difference, and so forth. * - * @param geom1 and geom2 are the geometry instances to be operated on. + * @param geom1 is the geometry instance to be operated on. + * @param geom2 is the geometry instance to be operated on. * @param sr The spatial reference to get the tolerance value from. * When sr is null, the tolerance is calculated from the input geometries. * @param progressTracker ProgressTracker instance that is used to cancel the lengthy operation. Can be null. diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index ca884b55..98c46738 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1118,28 +1118,28 @@ public void setYMax(double y) { m_envelope.ymax = y; } - @Override - public Geometry getBoundary() { - return Boundary.calculate(this, null); - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - Envelope1D interval = queryInterval(semantics, i); - if (interval.isEmpty()) { - interval.vmin = value; - interval.vmax = value; - setInterval(semantics, i, interval); - } - } - } - + @Override + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + Envelope1D interval = queryInterval(semantics, i); + if (interval.isEmpty()) { + interval.vmin = value; + interval.vmax = value; + setInterval(semantics, i, interval); + } + } + } + /** * The output of this method can be only used for debugging. It is subject to change without notice. */ diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 96540895..c9d0d259 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -133,8 +133,10 @@ public boolean contains(double v) { /** * Returns True if the envelope contains the other envelope (boundary * inclusive). Note: Will return false if either envelope is empty. + * @param other The other envelope. + * @return Return true if this contains the other. */ - public boolean contains(/* const */Envelope1D other) /* const */ + public boolean contains(Envelope1D other) { return other.vmin >= vmin && other.vmax <= vmax; } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index fa41db68..8e44dd33 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -131,6 +131,7 @@ public Envelope2D getInflated(double dx, double dy) { /** * Sets the envelope from the array of points. The envelope will be set to * empty if the array is null. + * @param points The points to set the envelope from. No element in the array can be null. */ public void setFromPoints(Point2D[] points) { if (points == null || points.length == 0) { @@ -198,6 +199,8 @@ else if (ymax < y) /** * Merges a point with this envelope without checking if the envelope is * empty. Use with care. + * @param x The x coord of the point + * @param y the y coord in the point */ public void mergeNE(double x, double y) { if (xmin > x) @@ -258,6 +261,7 @@ public void zoom(double factorX, double factorY) { /** * Checks if this envelope intersects the other. + * @param other The other envelope. * @return True if this envelope intersects the other. */ public boolean isIntersecting(Envelope2D other) { @@ -274,6 +278,7 @@ public boolean isIntersecting(Envelope2D other) { /** * Checks if this envelope intersects the other assuming neither one is empty. + * @param other The other envelope. * @return True if this envelope intersects the other. Assumes this and * other envelopes are not empty. */ @@ -289,6 +294,10 @@ public boolean isIntersectingNE(Envelope2D other) { /** * Checks if this envelope intersects the other. + * @param xmin_ + * @param ymin_ + * @param xmax_ + * @param ymax_ * @return True if this envelope intersects the other. */ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double ymax_) { @@ -307,7 +316,7 @@ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double y /** * Intersects this envelope with the other and stores result in this * envelope. - * + * @param other The other envelope. * @return True if this envelope intersects the other, otherwise sets this * envelope to empty state and returns False. */ @@ -370,6 +379,7 @@ public Point2D queryCorner(int index) { /** * Queries corners into a given array. The array length must be at least * 4. Starts from the lower left corner and goes clockwise. + * @param corners The array of four points. */ public void queryCorners(Point2D[] corners) { if ((corners == null) || (corners.length < 4)) @@ -399,6 +409,7 @@ public void queryCorners(Point2D[] corners) { * Queries corners into a given array in reversed order. The array length * must be at least 4. Starts from the lower left corner and goes * counterclockwise. + * @param corners The array of four points. */ public void queryCornersReversed(Point2D[] corners) { if (corners == null || ((corners != null) && (corners.length < 4))) @@ -500,6 +511,8 @@ public double getHeight() { /** * Moves the Envelope by given distance. + * @param dx + * @param dy */ public void move(double dx, double dy) { if (isEmpty()) @@ -558,6 +571,7 @@ public void queryUpperRight(Point2D pt) { /** * Returns True if this envelope is valid (empty, or has xmin less or equal * to xmax, or ymin less or equal to ymax). + * @return True if the envelope is valid. */ public boolean isValid() { return isEmpty() || (xmin <= xmax && ymin <= ymax); @@ -621,6 +635,8 @@ public boolean contains(double x, double y) { /** * Returns True if the envelope contains the other envelope (boundary * inclusive). + * @param other The other envelope. + * @return True if this contains the other. */ public boolean contains(Envelope2D other) {// Note: Will return False, if // either envelope is empty. @@ -630,7 +646,10 @@ public boolean contains(Envelope2D other) {// Note: Will return False, if /** * Returns True if the envelope contains the point (boundary exclusive). - */ + * @param x + * @param y + * @return True if this contains the point. + * */ public boolean containsExclusive(double x, double y) { // Note: This will return False, if envelope is empty, thus no need to // call is_empty(). @@ -647,6 +666,8 @@ public boolean containsExclusive(Point2D pt) { /** * Returns True if the envelope contains the other envelope (boundary * exclusive). + * @param other The other envelope + * @return True if this contains the other, boundary exclusive. */ boolean containsExclusive(Envelope2D other) { // Note: This will return False, if either envelope is empty, thus no @@ -1075,8 +1096,10 @@ public boolean isPointOnBoundary(Point2D pt, double tolerance) { /** * Calculates minimum distance from this envelope to the other. * Returns 0 for empty envelopes. + * @param other The other envelope. + * @return Returns the distance */ - public double distance(/* const */Envelope2D other) + public double distance(Envelope2D other) { return Math.sqrt(sqrDistance(other)); } @@ -1084,6 +1107,8 @@ public double distance(/* const */Envelope2D other) /** * Calculates minimum distance from this envelope to the point. * Returns 0 for empty envelopes. + * @param pt2D The other point. + * @return Returns the distance */ public double distance(Point2D pt2D) { @@ -1093,6 +1118,8 @@ public double distance(Point2D pt2D) /** * Calculates minimum squared distance from this envelope to the other. * Returns 0 for empty envelopes. + * @param other The other envelope. + * @return Returns the squared distance */ public double sqrDistance(Envelope2D other) { @@ -1122,6 +1149,11 @@ public double sqrDistance(Envelope2D other) /** * Calculates minimum squared distance from this envelope to the other. * Returns 0 for empty envelopes. + * @param xmin_ + * @param ymin_ + * @param xmax_ + * @param ymax_ + * @return Returns the squared distance. */ public double sqrDistance(double xmin_, double ymin_, double xmax_, double ymax_) { @@ -1178,6 +1210,8 @@ public double sqrMaxDistance(Envelope2D other) { /** * Calculates minimum squared distance from this envelope to the point. * Returns 0 for empty envelopes. + * @param pt2D The point. + * @return Returns the squared distance */ public double sqrDistance(Point2D pt2D) { diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 01614b14..8a71a236 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -167,7 +167,8 @@ protected static long estimateMemorySize(double[] attributes) } /** - * Returns the VertexDescription of this geomtry. + * Returns the VertexDescription of this geometry. + * @return VertexDescription */ public VertexDescription getDescription() { return m_description; @@ -176,6 +177,7 @@ public VertexDescription getDescription() { /** * Assigns the new VertexDescription by adding or dropping attributes. The * Geometry will have the src description as a result. + * @param src VertexDescription to assign. */ public void assignVertexDescription(VertexDescription src) { _touch(); @@ -191,6 +193,7 @@ public void assignVertexDescription(VertexDescription src) { * Merges the new VertexDescription by adding missing attributes from the * src. The Geometry will have a union of the current and the src * descriptions. + * @param src VertexDescription to merge. */ public void mergeVertexDescription(VertexDescription src) { _touch(); @@ -207,6 +210,8 @@ public void mergeVertexDescription(VertexDescription src) { /** * A shortcut for getDescription().hasAttribute() + * @param semantics The VertexDescription.Semantics to check. + * @return Return true if the attribute is present. */ public boolean hasAttribute(int semantics) { return getDescription().hasAttribute(semantics); @@ -215,7 +220,7 @@ public boolean hasAttribute(int semantics) { /** * Adds a new attribute to the Geometry. * - * @param semantics + * @param semantics The VertexDescription.Semantics to add. */ public void addAttribute(int semantics) { _touch(); @@ -231,6 +236,7 @@ public void addAttribute(int semantics) { * equivalent to setting the attribute to the default value for each vertex, * However, it is faster and the result Geometry has smaller memory * footprint and smaller size when persisted. + * @param semantics The VertexDescription.Semantics to drop. */ public void dropAttribute(int semantics) { _touch(); @@ -250,7 +256,10 @@ public void dropAllAttributes() { } /** - * Returns the min and max attribute values at the ordinate of the Geometry + * Returns the min and max attribute values at the ordinate of the Geometry. + * @param semantics The semantics of the interval. + * @param ordinate The ordinate of the interval. + * @return The interval. */ public abstract Envelope1D queryInterval(int semantics, int ordinate); @@ -262,19 +271,17 @@ public void dropAllAttributes() { */ public abstract void queryEnvelope(Envelope env); - // { - // Envelope2D e2d = new Envelope2D(); - // queryEnvelope2D(e2d); - // env.setEnvelope2D(e2d); - // } - /** * Returns tight bbox of the Geometry in X, Y plane. + * @param env + * The envelope to return the result in. */ public abstract void queryEnvelope2D(Envelope2D env); /** * Returns tight bbox of the Geometry in 3D. + * @param env + * The envelope to return the result in. */ abstract void queryEnvelope3D(Envelope3D env); @@ -282,6 +289,8 @@ public void dropAllAttributes() { * Returns the conservative bbox of the Geometry in X, Y plane. This is a * faster method than QueryEnvelope2D. However, the bbox could be larger * than the tight box. + * @param env + * The envelope to return the result in. */ public void queryLooseEnvelope2D(Envelope2D env) { queryEnvelope2D(env); @@ -291,6 +300,8 @@ public void queryLooseEnvelope2D(Envelope2D env) { * Returns tight conservative box of the Geometry in 3D. This is a faster * method than the QueryEnvelope3D. However, the box could be larger than * the tight box. + * @param env + * The envelope to return the result in. */ void queryLooseEnvelope3D(Envelope3D env) { queryEnvelope3D(env); @@ -328,13 +339,14 @@ void queryLooseEnvelope3D(Envelope3D env) { /** * Creates an instance of an empty geometry of the same type. + * @return The new instance. */ public abstract Geometry createInstance(); /** * Copies this geometry to another geometry of the same type. The result * geometry is an exact copy. - * + * @param dst The geometry instance to copy to. * @exception GeometryException * invalid_argument if the geometry is of different type. */ @@ -525,20 +537,22 @@ public Geometry copy() { return geom; } - /** - * Returns boundary of this geometry. - * - * Polygon and Envelope boundary is a Polyline. For Polyline and Line, the - * boundary is a Multi_point consisting of path endpoints. For Multi_point - * and Point NULL is returned. - */ - public abstract Geometry getBoundary(); - + /** + * Returns boundary of this geometry. + * + * Polygon and Envelope boundary is a Polyline. For Polyline and Line, the + * boundary is a Multi_point consisting of path end points. For Multi_point and + * Point null is returned. + * @return The boundary geometry. + */ + public abstract Geometry getBoundary(); + /** * Replaces NaNs in the attribute with the given value. * If the geometry is not empty, it adds the attribute if geometry does not have it yet, and replaces the values. * If the geometry is empty, it adds the attribute and does not set any values. - * + * @param semantics The semantics for which to replace the NaNs. + * @param value The value to replace NaNs with. */ public abstract void replaceNaNs(int semantics, double value); @@ -629,30 +643,31 @@ public String toString() { } } - /** - *Returns count of geometry vertices: - *1 for Point, 4 for Envelope, get_point_count for MultiVertexGeometry types, - *2 for segment types - *Returns 0 if geometry is empty. - */ - public static int vertex_count(Geometry geom) { - Geometry.Type gt = geom.getType(); - if (Geometry.isMultiVertex(gt.value())) - return ((MultiVertexGeometry)geom).getPointCount(); + /** + * Returns count of geometry vertices: 1 for Point, 4 for Envelope, + * get_point_count for MultiVertexGeometry types, 2 for segment types Returns 0 + * if geometry is empty. + * @param geom The geometry to get the vertex count for. + * @return The vertex count. + */ + public static int vertex_count(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (Geometry.isMultiVertex(gt.value())) + return ((MultiVertexGeometry) geom).getPointCount(); - if (geom.isEmpty()) - return 0; + if (geom.isEmpty()) + return 0; - if (gt == Geometry.Type.Envelope) - return 4; + if (gt == Geometry.Type.Envelope) + return 4; - if (gt == Geometry.Type.Point) - return 1; + if (gt == Geometry.Type.Point) + return 1; - if (Geometry.isSegment(gt.value())) - return 2; + if (Geometry.isSegment(gt.value())) + return 2; + + throw new GeometryException("missing type"); + } - throw new GeometryException("missing type"); - } - } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 6f729cea..99466fd2 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -88,8 +88,6 @@ public static MapGeometry jsonToGeometry(JsonReader json) { * reference). * @return The MapGeometry instance containing the imported geometry and its * spatial reference. - * @throws IOException - * @throws JsonParseException */ public static MapGeometry jsonToGeometry(String json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); @@ -153,8 +151,6 @@ public static String geometryToGeoJson(Geometry geometry) { * reference). * @return The MapGeometry instance containing the imported geometry and its * spatial reference. - * @throws IOException - * @throws JsonParseException */ public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); @@ -254,7 +250,7 @@ public static byte[] geometryToEsriShape(Geometry geometry) { * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. * @return The geometry. * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the WKT string. + * @throws IllegalArgumentException if an error is found while parsing the WKT string. */ public static Geometry geometryFromWkt(String wkt, int importFlags, Geometry.Type geometryType) { diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index d7161d52..dbd90646 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ package com.esri.core.geometry; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MAPGEOMETRY; + import java.io.Serializable; /** @@ -132,6 +134,22 @@ public boolean equals(Object other) { return true; } + /** + * Returns an estimate of this object size in bytes. + *