From 6022af1c175d125065cfaf6fa13369495bce797b Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 9 Sep 2013 10:32:49 -0700 Subject: [PATCH 001/145] Remove fixme comments (#7) --- src/com/esri/core/geometry/Envelope.java | 14 +- src/com/esri/core/geometry/InternalUtils.java | 3 +- src/com/esri/core/geometry/Line.java | 5 +- src/com/esri/core/geometry/MultiPath.java | 2 +- src/com/esri/core/geometry/MultiPathImpl.java | 96 +----- .../geometry/MultiVertexGeometryImpl.java | 57 ---- .../geometry/OperatorSimplifyLocalHelper.java | 21 -- .../geometry/PlaneSweepCrackerHelper.java | 12 +- .../core/geometry/PointInPolygonHelper.java | 2 - src/com/esri/core/geometry/Polygon.java | 5 - src/com/esri/core/geometry/PolygonUtils.java | 3 - src/com/esri/core/geometry/Segment.java | 5 - .../core/geometry/SegmentIteratorImpl.java | 2 - .../esri/core/geometry/VertexDescription.java | 6 +- .../VertexDescriptionDesignerImpl.java | 2 - .../esri/core/geometry/TestMultiPoint.java | 40 --- .../com/esri/core/geometry/TestPolygon.java | 287 ------------------ .../com/esri/core/geometry/TestSimplify.java | 3 - 18 files changed, 28 insertions(+), 537 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index e51c4ee8..d9336182 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -136,6 +136,14 @@ public void setCoords(double xmin, double ymin, double xmax, double ymax) { m_envelope.setCoords(xmin, ymin, xmax, ymax); } + /** + * Sets the envelope from the array of points. The result envelope is a + * bounding box of all the points in the array. If the array has zero + * length, the envelope will be empty. + * + * @param points + * The point array. + */ void setCoords(Point[] points) { _touch(); setEmpty(); @@ -700,10 +708,8 @@ void _afterAddAttributeImpl(int semantics) {// copied from static void _attributeCopy(double[] src, int srcStart, double[] dst, int dstStart, int count) { - // FIXME performance!!!! - // System.arraycopy(src, srcStart, dst, dstStart, count); - for (int i = 0; i < count; i++) - dst[dstStart + i] = src[i + srcStart]; + if (count > 0) + System.arraycopy(src, srcStart, dst, dstStart, count); } double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { diff --git a/src/com/esri/core/geometry/InternalUtils.java b/src/com/esri/core/geometry/InternalUtils.java index 81ebd259..8ba01659 100644 --- a/src/com/esri/core/geometry/InternalUtils.java +++ b/src/com/esri/core/geometry/InternalUtils.java @@ -108,9 +108,8 @@ void shiftPath(MultiPath inputGeom, int iPath, double shift) { int i1 = inputGeom.getPathStart(iPath); int i2 = inputGeom.getPathEnd(iPath); - Point2D pt = new Point2D();// = null; + Point2D pt = new Point2D(); - // FIXME test to see if Point2D should be null while (i1 < i2) { xyStream.read(i1, pt); pt.x += shift; diff --git a/src/com/esri/core/geometry/Line.java b/src/com/esri/core/geometry/Line.java index 6063caf5..f4f2db08 100644 --- a/src/com/esri/core/geometry/Line.java +++ b/src/com/esri/core/geometry/Line.java @@ -560,9 +560,7 @@ boolean equals(Line other) { if (other == this) return true; - // FIXME review use of Java's instance of to see if it complies with - // Borgs - if (other instanceof Line)// (!IS_INSTANCE_OF(other, Line)) + if (other instanceof Line) return false; return _equalsImpl((Segment) other); @@ -992,7 +990,6 @@ int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { return 0; } - // FIXME In Native borg this has no implementation @Override void _copyToImpl(Segment dst) { // TODO Auto-generated method stub diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java index d0749652..eca929d6 100644 --- a/src/com/esri/core/geometry/MultiPath.java +++ b/src/com/esri/core/geometry/MultiPath.java @@ -599,7 +599,7 @@ void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { * Closes last path of the MultiPath with the Arc Segment. */ void closePathWithArc() { - throw new RuntimeException("not implemented"); /* FIXME */ + throw new RuntimeException("not implemented"); } /** diff --git a/src/com/esri/core/geometry/MultiPathImpl.java b/src/com/esri/core/geometry/MultiPathImpl.java index e8062f3a..a8058804 100644 --- a/src/com/esri/core/geometry/MultiPathImpl.java +++ b/src/com/esri/core/geometry/MultiPathImpl.java @@ -131,7 +131,6 @@ public void startPath(Point3D point) { // Reviewed vs. Native Jan 11, 2011 public void startPath(Point point) { if (point.isEmpty()) - // FIXME exc throw new IllegalArgumentException();// throw new // IllegalArgumentException(); @@ -292,18 +291,15 @@ public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, public void openPath(int pathIndex) { _touch(); if (m_bPolygon) - // FIXME exc throw new GeometryException("internal error");// do not call this // method on a // polygon int pathCount = getPathCount(); if (pathIndex > getPathCount()) - // FIXME exc throw new IllegalArgumentException(); if (m_pathFlags == null) - // FIXME exc throw new GeometryException("internal error"); m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); @@ -359,7 +355,6 @@ public void openPathAndDuplicateStartVertex(int pathIndex) { public void openAllPathsAndDuplicateStartVertex() { _touch(); if (m_bPolygon) - // FIXME throw new GeometryException("internal error");// do not call this // method on a // polygon @@ -579,15 +574,13 @@ public void addSegment(Segment segment, boolean bStartNewPath) { if (segment.getType() == Type.Line) { Point point = new Point(); if (bStartNewPath || isEmpty()) { - // FIXME change getStart to queryStart!!!!!!! segment.queryStart(point); startPath(point); } - // FIXME change getStart to queryEnd + segment.queryEnd(point); lineTo(point); } else { - // FIXME throw new GeometryException("internal error"); } } @@ -665,9 +658,6 @@ public void add(MultiPathImpl src, boolean bReversePaths) { addPath(src, i, !bReversePaths); } - // Reviewed vs. Native Jan 11, 2011 - // FIXME THERE IS POTENTIALLY A BUG WITH the use of AttributeStream - // InsertRange public void addPath(MultiPathImpl src, int srcPathIndex, boolean bForward) { insertPath(-1, src, srcPathIndex, bForward); } @@ -677,56 +667,6 @@ public void addPath(Point2D[] _points, int count, boolean bForward) { insertPath(-1, _points, 0, count, bForward); } - // FIXME add add to attributestream base - // public void addPath(double[][] _points, int count, boolean bForward) - // { - // m_bPathStarted = false; - // - // int oldPointCount = m_pointCount; - // _verifyAllStreams(); - // - // int newPointCount = oldPointCount + count; - // if (oldPointCount > 0) - // m_paths.add(newPointCount); - // else - // { - // // _ASSERT(m_paths.size() == 2); - // m_paths.write(1, newPointCount); - // } - // - // _resizeImpl(newPointCount); - // - // _verifyAllStreams(); - // - // if (m_segmentParamIndex != null) - // { - // m_segmentParamIndex.resize(m_pointCount, -1); - // m_segmentFlags.resize(m_pointCount, (byte)SegmentFlags.enumLineSeg); - // } - // - // if (oldPointCount > 0) - // m_pathFlags.add(0); - // - // // _ASSERT(m_pathFlags.size() == m_paths.size()); - // - // if (m_bPolygon) - // { - // //Marc the path as closed - // m_pathFlags.write(m_pathFlags.size() - 2, (byte)PathFlags.enumClosed); - // } - // - // int j = oldPointCount; - // AttributeStreamOfDbl points = - // (AttributeStreamOfDbl)m_vertexAttributes[0]; - // for (int i = 0; i < count; i++, j++) - // { - // int index = (bForward ? i : count - i - 1); - // points.write(2 * j, _points[index][0]); - // points.write(2 * j + 1, _points[index][1]); - // } - // } - // - public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, int src_segment_from, int src_segment_count, boolean b_start_new_path) { @@ -866,7 +806,6 @@ public void reversePath(int pathIndex) { _verifyAllStreams(); int pathCount = getPathCount(); if (pathIndex >= pathCount) - // FIXME exc throw new IllegalArgumentException(); int reversedPathStart = getPathStart(pathIndex); @@ -1797,29 +1736,24 @@ public void applyTransformation(Transformation2D transform, int pathIndex) { case SegmentFlags.enumBezierSeg: { ptControl.x = m_segmentParams.read(segIndex); ptControl.y = m_segmentParams.read(segIndex + 1); - // FIXME rohit has transform returning the object rather - // than transforming the input transform.transform(ptControl, ptControl); m_segmentParams.write(segIndex, ptControl.x); m_segmentParams.write(segIndex + 1, ptControl.y); ptControl.x = m_segmentParams.read(segIndex + 3); ptControl.y = m_segmentParams.read(segIndex + 4); - // FIXME rohit has transform returning the object rather - // than transforming the input transform.transform(ptControl, ptControl); m_segmentParams.write(segIndex + 3, ptControl.x); m_segmentParams.write(segIndex + 4, ptControl.y); } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error");// FIXME + throw new GeometryException("internal error"); } } } - // FIXME rohit has transform returning the object rather than - // transforming the input + transform.transform(ptStart, ptStart); points.write(ipoint * 2, ptStart.x); points.write(ipoint * 2 + 1, ptStart.y); @@ -1872,11 +1806,12 @@ public void applyTransformation(Transformation3D transform) { } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error");// FIXME + throw new GeometryException("internal error"); } } } + ptStart = transform.transform(ptStart); points.write(ipoint * 2, ptStart.x); points.write(ipoint * 2 + 1, ptStart.y); @@ -1911,7 +1846,6 @@ void _copyToImpl(MultiVertexGeometryImpl dst) { dstPoly.m_bPathStarted = false; dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; - // FIXME there is no cloning in here. Is this necessary? if (m_paths != null) dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); else @@ -1968,25 +1902,6 @@ public double calculateLength2D() { return len.getResult(); } - // FIXME figure out hascode - // int getHashCode() - // { - // int hashCode = MultiVertexGeometryImpl.getHashCode(); - // - // if (!isEmptyImpl()) - // { - // int pathCount = getPathCount(); - // - // if (m_paths != null) - // m_paths.calculateHashImpl(hashCode, 0, pathCount + 1); - // - // if (m_pathFlags != null) - // m_pathFlags.calculateHashImpl(hashCode, 0, pathCount); - // } - // - // return hashCode; - // } - @Override public boolean equals(Object other) { if (other == this) @@ -2026,7 +1941,6 @@ public boolean equals(Object other) { */ public SegmentIteratorImpl querySegmentIteratorAtVertex(int startVertexIndex) { if (startVertexIndex < 0 || startVertexIndex >= getPointCount()) - // FIXME throw new IndexOutOfBoundsException(); SegmentIteratorImpl iter = new SegmentIteratorImpl(this, diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java index 9b5ce354..80c69315 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -292,12 +292,9 @@ public void setXY(int index, double x, double y) { @Override public Point3D getXYZ(int index) { if (index < 0 || index >= getPointCount()) - // FIXME exc throw new IndexOutOfBoundsException(); _verifyAllStreams(); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; Point3D pt = new Point3D(); pt.x = v.read(index * 2); @@ -316,15 +313,12 @@ public Point3D getXYZ(int index) { @Override public void setXYZ(int index, Point3D pt) { if (index < 0 || index >= getPointCount()) - // FIXME exc throw new IndexOutOfBoundsException(); addAttribute(Semantics.Z); _verifyAllStreams(); notifyModified(DirtyFlags.DirtyCoordinates); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; v.write(index * 2, pt.x); v.write(index * 2 + 1, pt.y); @@ -335,12 +329,10 @@ public void setXYZ(int index, Point3D pt) { @Override public double getAttributeAsDbl(int semantics, int offset, int ordinate) { if (offset < 0 || offset >= m_pointCount) - // FIXME exc throw new IndexOutOfBoundsException(); int ncomps = VertexDescription.getComponentCount(semantics); if (ordinate >= ncomps) - // FIXME exc throw new IndexOutOfBoundsException(); _verifyAllStreams(); @@ -366,12 +358,10 @@ public int getAttributeAsInt(int semantics, int offset, int ordinate) { public void setAttribute(int semantics, int offset, int ordinate, double value) { if (offset < 0 || offset >= m_pointCount) - // FIXME exc throw new IndexOutOfBoundsException(); int ncomps = VertexDescription.getComponentCount(semantics); if (ordinate >= ncomps) - // FIXME exc throw new IndexOutOfBoundsException(); addAttribute(semantics); @@ -388,8 +378,6 @@ public void setAttribute(int semantics, int offset, int ordinate, int value) { setAttribute(semantics, offset, ordinate, (double) value); } - // FIXME change semantics to an enum - // Checked vs. Jan 11, 2011 public AttributeStreamBase getAttributeStreamRef(int semantics) { throwIfEmpty(); @@ -400,8 +388,6 @@ public AttributeStreamBase getAttributeStreamRef(int semantics) { return m_vertexAttributes[attributeIndex]; } - // FIXME change semantics to an enum - // Checked vs. Jan 11, 2011 /** * Sets a reference to the given AttributeStream of the Geometry. Once the * buffer has been obtained, the vertices of the Geometry can be manipulated @@ -424,7 +410,6 @@ public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { if ((stream != null) && VertexDescription.getPersistence(semantics) != stream .getPersistence())// input stream has wrong persistence - // FIXME exc throw new IllegalArgumentException(); // Do not check for the stream size here to allow several streams to be @@ -641,7 +626,6 @@ public boolean equals(Object other) { */ public void setEnvelope(Envelope env) { if (!m_description.equals(env.getDescription())) - // FIXME exc throw new IllegalArgumentException(); // m_envelope = (Envelope) env.clone(); @@ -690,15 +674,10 @@ void _copyToUnsafe(MultiVertexGeometryImpl dst) { dst.m_flagsMask = m_flagsMask; dst.m_vertexAttributes = cloneAttributes; - // FIXME accelerators - // if(m_accelerators != null) - // dst.m_accelerators = m_accelerators; - try { _copyToImpl(dst); // copy child props } catch (Exception ex) { dst.setEmpty(); - // TODO fix exception throw new RuntimeException(ex); } } @@ -735,7 +714,6 @@ public void notifyModified(int flags) { } m_flagsMask |= flags; - // FIXME acceler _clearAccelerators(); _touch(); } @@ -853,10 +831,6 @@ protected void _verifyAllStreamsImpl() { m_vertexAttributes[attributeIndex] = AttributeStreamBase .createAttributeStreamWithSemantics(semantics, m_pointCount); - // FIXME when attribute stream is updated, update this code. - // m_vertexAttributes[attributeIndex] = - // AttributeStreamBase.createAttributeStream(semantics, - // m_pointCount); m_reservedPointCount = m_pointCount; } } @@ -1034,41 +1008,12 @@ void _interpolateTwoVertices(int vertex1, int vertex2, double f, return pt.length(); } - // FIXME Remove this method. It is not in the MultiVertexGeometryImpl... - - // /** - // * Returns a reference to the given AttributeStream of the Geometry. Once - // * the stream has been obtained, the vertices of the Geometry can be - // * manipulated directly. Call notifyModified, when finished. The method - // * allocates the stream if not present. - // * - // * @param semantics - // * Semantics of the attribute to return stream for. - // * @throws Throws - // * empty_geometry for the empty geometry. - // */ - // public AttributeStreamBase getAttributeStream(int semantics) { - // if (isEmpty()) - // throw new GeometryException( - // "This operation was performed on an Empty Geometry."); - // - // addAttribute(semantics); - // _verifyAllStreams(); - // - // int attributeIndex = m_description.getAttributeIndex(semantics); - // return m_vertexAttributes[attributeIndex]; - // } - - // FIXME // ////////////////// METHODS To REMOVE /////////////////////// @Override public Point getPoint(int index) { if (index < 0 || index >= m_pointCount) throw new IndexOutOfBoundsException(); - // _ASSERT(!IsEmpty()); - // _ASSERT(m_vertexAttributes != null); - _verifyAllStreams(); Point outPoint = new Point(); @@ -1149,8 +1094,6 @@ public void queryCoordinates(Point3D[] dst) { } } - // FIXME - public abstract boolean _buildRasterizedGeometryAccelerator( double toleranceXY, GeometryAccelerationDegree accelDegree); diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index 2f726e53..305b2d9d 100644 --- a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -1652,27 +1652,6 @@ MultiVertexGeometry simplifyPlanar_() { m_progressTracker); } - // if (false)// FIXME:do not forget to change this to if(false)!!! - // { - // OperatorSimplify simplify = (OperatorSimplify) - // (OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify)); - // NonSimpleResult nsres = new NonSimpleResult(); - // Geometry geometry = - // m_editShape.getGeometry(m_editShape.getFirstGeometry());// extract - // the result of simplify - // boolean res = simplify.isSimpleAsFeature(geometry, m_sr, true, nsres, - // null); - // if (false) - // { - // ((MultiPathImpl)geometry._getImpl()).saveToTextFileDbg("c:/temp/_simplifyDbg.txt"); - // } - // if (!res) - // { - // assert (nsres.m_reason.compareTo(NonSimpleResult.Reason.CrossOver) >= - // 0); - // } - // } - if (m_geometry.getType().equals(Geometry.Type.Polygon)) { Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), m_knownSimpleResult); diff --git a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 75e01a50..175f485a 100644 --- a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -780,8 +780,10 @@ void processSplitHelper1_(int index, int edge, int clusterStart = getEdgeCluster(edge, 0); Point2D pt = getClusterXY(clusterStart); if (!pt.isEqual(newStart)) { - if (pt.compare(m_sweep_point) > 0 - && newStart.compare(m_sweep_point) < 0) { + int res1 = pt.compare(m_sweep_point); + int res2 = newStart.compare(m_sweep_point); + //if (pt.compare(m_sweep_point) > 0 && newStart.compare(m_sweep_point) < 0) + if (res1 * res2 < 0) { m_complications = true;// point is not yet have been processed // but moved before the sweep point, // this will require @@ -799,8 +801,10 @@ void processSplitHelper1_(int index, int edge, int clusterEnd = getEdgeCluster(edge, 1); pt = getClusterXY(clusterEnd); if (!pt.isEqual(newEnd)) { - if (pt.compare(m_sweep_point) > 0 - && newEnd.compare(m_sweep_point) < 0) { + int res1 = pt.compare(m_sweep_point); + int res2 = newEnd.compare(m_sweep_point); + //if (pt.compare(m_sweep_point) > 0 && newEnd.compare(m_sweep_point) < 0) + if (res1 * res2 < 0) { m_complications = true;// point is not yet have been processed // but moved before the sweep point. } diff --git a/src/com/esri/core/geometry/PointInPolygonHelper.java b/src/com/esri/core/geometry/PointInPolygonHelper.java index 1d4e6986..0cda4401 100644 --- a/src/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/com/esri/core/geometry/PointInPolygonHelper.java @@ -275,8 +275,6 @@ else if (hit == RasterizedGeometry2D.HitType.Outside) } } - // FIXME, the rest of the point in polygon tests are set up to avoid - // Point2D. This is the last bit return _isPointInPolygonInternal(inputPolygon, new Point2D( inputPointXVal, inputPointYVal), tolerance); } diff --git a/src/com/esri/core/geometry/Polygon.java b/src/com/esri/core/geometry/Polygon.java index 18e96249..2e173acd 100644 --- a/src/com/esri/core/geometry/Polygon.java +++ b/src/com/esri/core/geometry/Polygon.java @@ -73,11 +73,6 @@ public double calculateRingArea2D(int ringIndex) { return m_impl.calculateRingArea2D(ringIndex); } - // FIXME are these a java requirement? - // int getWKBPolygonCount() { return m_impl.getWKBPolygonCount(); } - // void setKnownRingOrientation(boolean bYesNo) { - // m_impl.setKnownRingOrientation(bYesNo); } - /** * Returns TRUE if the ring is an exterior ring. Valid only for simple * polygons. diff --git a/src/com/esri/core/geometry/PolygonUtils.java b/src/com/esri/core/geometry/PolygonUtils.java index 3c5bb5ca..e8f700ab 100644 --- a/src/com/esri/core/geometry/PolygonUtils.java +++ b/src/com/esri/core/geometry/PolygonUtils.java @@ -253,9 +253,6 @@ static boolean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, if (!segIter.nextPath() || !segIter.hasNextSegment()) throw new GeometryException("corrupted geometry"); - // FIXME java enums can't cast to ints? what the hell?! - // enum_class PiPResult { PiPOutside = 0, PiPInside = 1, PiPBoundary = - // 2}; int res = 2;// 2(int)PiPResult.PiPBoundary; while (res == 2 /* (int)PiPResult.PiPBoundary */ diff --git a/src/com/esri/core/geometry/Segment.java b/src/com/esri/core/geometry/Segment.java index 501f7dda..4f9b131d 100644 --- a/src/com/esri/core/geometry/Segment.java +++ b/src/com/esri/core/geometry/Segment.java @@ -539,9 +539,7 @@ private void _get(int endPoint, Point outPoint) { if (isEmptyImpl()) throw new GeometryException("empty geometry");// ._setToDefault(); - // FIXME Native has this line repeated twice! Check up on this. outPoint.assignVertexDescription(m_description); - // outPoint.assignVertexDescription(m_description); if (outPoint.isEmptyImpl()) outPoint._setToDefault(); @@ -576,7 +574,6 @@ private void _set(int endPoint, Point src) { } double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { - // FIXME review rohits impl. Only an assertion in native if (isEmptyImpl()) throw new GeometryException( "This operation was performed on an Empty Geometry."); @@ -737,7 +734,6 @@ void reverse() { m_yEnd = origyStart; for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - // FIXME fix stupid semantics enum int semantics = m_description.getSemantics(i);// VertexDescription.Semantics // semantics = // m_description.getSemantics(i); @@ -869,7 +865,6 @@ abstract double getClosestCoordinate(Point2D inputPoint, public abstract int intersectionWithAxis2D(boolean bAxisX, double ordinate, double[] resultOrdinates, double[] parameters); - // FIXME Ask sergey what this method is for void _reverseImpl() { } diff --git a/src/com/esri/core/geometry/SegmentIteratorImpl.java b/src/com/esri/core/geometry/SegmentIteratorImpl.java index 644a6767..af1e62b5 100644 --- a/src/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/com/esri/core/geometry/SegmentIteratorImpl.java @@ -404,8 +404,6 @@ public void _updateSegment() { AttributeStreamOfInt8 segFlagStream = m_parent .getSegmentFlagsStreamRef(); - // FIXME Review this implementation of segment flags and the switch - // statement below. int segFlag = SegmentFlags.enumLineSeg; if (segFlagStream != null) segFlag = (segFlagStream.read(startVertexIndex) & SegmentFlags.enumSegmentMask); diff --git a/src/com/esri/core/geometry/VertexDescription.java b/src/com/esri/core/geometry/VertexDescription.java index b10afe2e..296e2af3 100644 --- a/src/com/esri/core/geometry/VertexDescription.java +++ b/src/com/esri/core/geometry/VertexDescription.java @@ -59,16 +59,14 @@ public class VertexDescription { static int[] _interpolation = { Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.NONE, Interpolation.ANGULAR, Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.NONE, // FIXME this last value doesnt exist in native + Interpolation.NONE, }; static int[] _persistence = { Persistence.enumDouble, Persistence.enumDouble, Persistence.enumDouble, Persistence.enumInt32, Persistence.enumFloat, Persistence.enumFloat, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumInt32, // FIXME, this last - // Int32 doesn't - // exist in native + Persistence.enumFloat, Persistence.enumInt32, }; static int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index d7570b0c..3ead4792 100644 --- a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -152,8 +152,6 @@ VertexDescription _createInternal() { protected void _initMapping() { m_attributeCount = 0; - // FIXME native has for loop for (int i = 0, j = 0; i < - // Semantics.MAXSEMANTICS + 1; i++) for (int i = 0, j = 0; i < Semantics.MAXSEMANTICS; i++) { if (m_semanticsToIndexMap[i] >= 0) { m_semantics[j] = i; diff --git a/unittest/com/esri/core/geometry/TestMultiPoint.java b/unittest/com/esri/core/geometry/TestMultiPoint.java index 44c927f6..b5b7d534 100644 --- a/unittest/com/esri/core/geometry/TestMultiPoint.java +++ b/unittest/com/esri/core/geometry/TestMultiPoint.java @@ -77,35 +77,9 @@ public static void testCreation() { MultiPoint mpoint = new MultiPoint(); assertTrue(mpoint != null); - // FIXME uncomment when assertions are fixed - // try - // { - // // OutputDebugString(L"Test an assert\n"); - // // GeometryException::m_assertOnException = false; - // - // Point pt2 = mpoint.getPoint(0);; - // } - // catch(GeometryException except) - // { - // assertTrue(except.index_out_of_bounds); - // GeometryException::m_assertOnException = true; - // } MultiPoint mpoint1 = new MultiPoint(); assertTrue(mpoint1 != null); - // FIXME uncomment when assertions are fixed - // try - // { - // OutputDebugString(L"Test an assert\n"); - // GeometryException::m_assertOnException = false; - // Point ppp; - // mpoint.getPointByVal(0, ppp); - // } - // catch(GeometryException except) - // { - // assertTrue(except.index_out_of_bounds); - // GeometryException::m_assertOnException = true; - // } mpoint.setEmpty(); Point pt = new Point(0, 0); @@ -129,20 +103,6 @@ public static void testCreation() { mpoint.add(pt); Point pt3 = mpoint.getPoint(0); assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 0 */); - // assertFalse(mpoint->HasAttribute(VertexDescription::Semantics::Z)); - // FIXME once transform3D is public - // Transformation3D transform3D = GCNEW Transformation3D; - // transform3D.setTranslate(1, 1, 0); - // mpoint.applyTransformation(transform3D); - - // assertTrue(mpoint->HasAttribute(VertexDescription::Semantics::Z)); - // pt3 = mpoint.getPoint(0); - // assertTrue(pt3.x == 1 && pt3.y == 1 && pt3.z == 0); - // transform3D.setTranslate(56, 12, 333); - // mpoint.applyTransformation(transform3D); - // pt3 = mpoint.getXYZ(0); - // assertTrue(pt3.x == 57 && pt3.y == 13 && pt3.z == 333); - // CompareGeometryContent(mpoint, &pt3, 1); } { // test QueryInterval diff --git a/unittest/com/esri/core/geometry/TestPolygon.java b/unittest/com/esri/core/geometry/TestPolygon.java index 450ac083..22d9617c 100644 --- a/unittest/com/esri/core/geometry/TestPolygon.java +++ b/unittest/com/esri/core/geometry/TestPolygon.java @@ -77,147 +77,6 @@ public void testCreation1() { number = poly.getStateFlag(); assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); number = poly.getStateFlag(); - - // FIXME - // env.queryCoornerByVal(index, ptDst) - // SPtrOfArrayOf(Point2D) corners = new ArrayOf(Point2D)(4); - // env.QueryCorners(corners); - // poly.setEmpty(); - // poly.addPath(corners, corners.length, true); - - // assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); - - // { - // SegmentIterator segIter1 = poly.querySegmentIterator(); - // while (segIter1.nextPath()) - // { - // while (segIter1.hasNextSegment()) - // { - // Segment seg = segIter1.nextSegment(); - // double len = seg.calculateLength2D(); - // assertTrue(len != 0); - // } - // } - // } - - // env.QueryCornersReversed(corners); - // poly.setEmpty(); - // poly.addPath(corners, corners.length, true); - // assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); - // - // poly.setEmpty(); - // env.SetCoords(-200, -200, 200, 200); - // poly.addEnvelope(env, false); - // env.SetCoords(-100, -100, 100, 100); - // poly.addEnvelope(env, true); - // assertTrue(Math.abs(poly.calculateArea2D() - (400 * 400 - 200 * 200)) - // < 1e-12); - // assertTrue(Math.abs(poly.calculateRingArea2D(1) - (- 200 * 200)) < - // 1e-12); - // assertTrue(Math.abs(poly.calculateRingArea2D(0) - (400 * 400)) < - // 1e-12); - // assertTrue(Math.abs(poly.calculateLength2D() - (400 * 4 + 200 * 4)) < - // 1e-12); - - // test CopyTo; - // Polygon polyCopy = new Polygon(); - // poly.copyTo(polyCopy); - // assertTrue(poly.calculateArea2D() == polyCopy.calculateArea2D()); - // assertTrue(poly.calculateRingArea2D(1) == - // polyCopy.calculateRingArea2D(1)); - // assertTrue(poly.calculateRingArea2D(1) == - // polyCopy.calculateRingArea2D(1)); - // assertTrue(poly.calculateLength2D() == polyCopy.calculateLength2D()); - // } - // - // Polygon poly = new Polygon(); - // poly.startPath(10, 1); - // poly.lineTo(15, 20); - // poly.lineTo(30, 14); - // poly.lineTo(60, 144); - // - // assertTrue(poly.getPointCount() == 4); - // assertTrue(poly.getPathCount() == 1); - // SPtrOfArrayOf(Point2D) xy = poly.getCoordinates2D(); - // assertTrue(xy[0].x == 10); assertTrue(xy[0].y == 1); - // assertTrue(xy[1].x == 15); assertTrue(xy[1].y == 20); - // assertTrue(xy[2].x == 30); assertTrue(xy[2].y == 14); - // assertTrue(xy[3].x == 60); assertTrue(xy[3].y == 144); - - // poly.startPath(20, 13); - // poly.lineTo(150, 120); - // poly.lineTo(300, 414); - // poly.lineTo(610, 14); - // poly.lineTo(6210, 140); - // - // assertTrue(poly.getPointCount() == 9); - // assertTrue(poly.getPathCount() == 2); - // assertTrue(poly.isClosedPath(0)); - // assertTrue(poly.isClosedPath(1)); - // assertFalse(poly.hasNonLinearSegments(0)); - // assertFalse(poly.hasNonLinearSegments(1)); - // - // { - // SegmentIterator segIter1 = poly.querySegmentIterator(); - // while (segIter1.nextPath()) - // { - // while (segIter1.hasNextSegment()) - // { - // Segment seg = segIter1.nextSegment(); - // double len = seg.calculateLength2D(); - // assertTrue(len != 0); - // } - // } - // } - - // { - // MultiPathImpl::Pointer mpImpl = - // (MultiPathImpl::Pointer)poly->_GetImpl(); - // AttributeStreamBase xy = - // mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, - // Semantics, POSITION)); - // double x = xy.readAsDbl(2 * 0); - // double y = xy.readAsDbl(2 * 0 + 1); - // assertTrue(x == 10); assertTrue(y == 1); - // x = xy.readAsDbl(2 * 1); - // y = xy.readAsDbl(2 * 1 + 1); - // assertTrue(x == 15); assertTrue(y == 20); - // x = xy.readAsDbl(2 * 2); - // y = xy.readAsDbl(2 * 2 + 1); - // assertTrue(x == 30); assertTrue(y == 14); - // x = xy.readAsDbl(2 * 3); - // y = xy.readAsDbl(2 * 3 + 1); - // assertTrue(x == 60); assertTrue(y == 144); - // - // x = xy.readAsDbl(2 * 4); - // y = xy.readAsDbl(2 * 4 + 1); - // assertTrue(x == 20); assertTrue(y == 13); - // x = xy.readAsDbl(2 * 5); - // y = xy.readAsDbl(2 * 5 + 1); - // assertTrue(x == 150); assertTrue(y == 120); - // x = xy.readAsDbl(2 * 6); - // y = xy.readAsDbl(2 * 6 + 1); - // assertTrue(x == 300); assertTrue(y == 414); - // x = xy.readAsDbl(2 * 7); - // y = xy.readAsDbl(2 * 7 + 1); - // assertTrue(x == 610); assertTrue(y == 14); - // x = xy.readAsDbl(2 * 8); - // y = xy.readAsDbl(2 * 8 + 1); - // assertTrue(x == 6210); assertTrue(y == 140); - // - // assertTrue(Math.abs(mpImpl.calculateArea2D() - 71752.5) < 1e-6); - // assertTrue(Math.abs(mpImpl.calculateLength2D() - 13117.917692934170) - // < 1e-6); - // - // AttributeStreamOfIndexType parts = mpImpl.getPathStreamRef(); - // assertTrue(parts.size() == 3); - // assertTrue(parts.read(0) == 0); - // assertTrue(parts.read(1) == 4); - // assertTrue(parts.read(2) == 9); - // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); - // } } @Test @@ -299,17 +158,6 @@ public void testCreation2() { poly2.lineTo(100, 10); poly2.lineTo(100, 100); poly2.lineTo(10, 100); - - // FIXME - // RasterizedGeometry2D rg = - // RasterizedGeometry2D.create((Geometry)poly2, 0, 1024); - // RasterizedGeometry2D.HitType res = null; - // res = rg.queryPointInGeometry(7, 10); - // assertTrue(res == RasterizedGeometry2D.HitType.Outside); - // res = rg.queryPointInGeometry(10, 10); - // assertTrue(res == RasterizedGeometry2D.HitType.Border); - // res = rg.queryPointInGeometry(50, 50); - // assertTrue(res == RasterizedGeometry2D.HitType.Inside); } { @@ -933,17 +781,6 @@ public void testInsertPath() { Point2D pt3 = poly.getXY(3); assertTrue(pt3.x == 16 && pt3.y == 21); - // SPtrOfArrayOf(Point2D) points = NULLPTR; - // FIXME - // poly.insertPath(1, points, 0, 0, true); - // assertTrue(poly.getPathCount() == 5); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 4); - // assertTrue(poly.getPathStart(3) == 8); - // assertTrue(poly.getPathStart(4) == 12); - // assertTrue(poly.getPointCount() == 16); - Point pt2d = new Point(-27, -27); poly.insertPoint(1, 0, pt2d); @@ -953,44 +790,6 @@ public void testInsertPath() { assertTrue(poly.getPathStart(2) == 9); assertTrue(poly.getPathStart(3) == 13); assertTrue(poly.getPointCount() == 17); - - // points = new ArrayOf(Point2D)(3); - // points[0].x = 17; - // points[0].y = 17; - // points[1].x = 19; - // points[1].y = 19; - // points[2].x = 23; - // points[2].y = 23; - - // poly.insertPath(1, points, 0, 3, true); - // assertTrue(poly.getPathCount() == 6); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 7); - // assertTrue(poly.getPathStart(3) == 8); - // assertTrue(poly.getPathStart(4) == 12); - // assertTrue(poly.getPathStart(5) == 16); - // assertTrue(poly.getPointCount() == 20); - - // Point2D *pointsNative = new Point2D[3]; - // pointsNative[0].x = 29; - // pointsNative[0].y = 29; - // pointsNative[1].x = 31; - // pointsNative[1].y = 31; - // pointsNative[2].x = 37; - // pointsNative[2].y = 37; - - // FIXME - // poly.insertPath(1, pointsNative, 0, 3, true); - // assertTrue(poly.getPathCount() == 7); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 7); - // assertTrue(poly.getPathStart(3) == 10); - // assertTrue(poly.getPathStart(4) == 11); - // assertTrue(poly.getPathStart(5) == 15); - // assertTrue(poly.getPathStart(6) == 19); - // assertTrue(poly.getPointCount() == 23); } @Test @@ -1222,99 +1021,13 @@ public void testInsertPointsFromArray() { poly1.lineTo(300, 147); poly1.lineTo(6000, 1447); - // FIXME - // poly1.insertPoints(1, 2, arr, 1, 3, true); //forward - assertTrue(poly1.getPathCount() == 2); assertTrue(poly1.getPathStart(1) == 4); assertTrue(poly1.isClosedPath(0)); assertTrue(poly1.isClosedPath(1)); - // assertTrue(poly1.getPointCount() == 11); - // assertTrue(poly1.getPathSize(1) == 7); - // Point2D ptOut; - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 314 && ptOut.y == 217); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 300 && ptOut.y == 147); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); - - // Point2D *points = new Point2D[3]; - // points[0].x = 17; - // points[0].y = 17; - // points[1].x = 19; - // points[1].y = 19; - // points[2].x = 23; - // points[2].y = 23; - - // FIXME - // poly1.insertPoints(1, 2, points, 0, 3, true); - // assertTrue(poly1.getPathCount() == 2); - // assertTrue(poly1.getPathStart(1) == 4); - // assertTrue(poly1.getPointCount() == 14); - // assertTrue(poly1.getPathSize(1) == 10); - - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 17 && ptOut.y == 17); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 19 && ptOut.y == 19); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 23 && ptOut.y == 23); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); } {// Test reversed insertion of an array of Point2D - // ArrayOf(Point2D) arr = new ArrayOf(Point2D)(5); - // arr[0].SetCoords(10, 1); - // arr[1].SetCoords(15, 20); - // arr[2].SetCoords(300, 14); - // arr[3].SetCoords(314, 217); - // arr[4].SetCoords(60, 144); - - // Polygon poly1 = new Polygon(); - // poly1.startPath(1, 17); - // poly1.lineTo(1, 207); - // poly1.lineTo(3, 147); - // poly1.lineTo(6, 1447); - // - // poly1.startPath(1000, 17); - // poly1.lineTo(1250, 207); - // poly1.lineTo(300, 147); - // poly1.lineTo(6000, 1447); - // - // //FIXME - // //poly1.insertPoints(1, 2, arr, 1, 3, false); //reversed - // - // assertTrue(poly1.getPathCount() == 2); - // assertTrue(poly1.getPathStart(1) == 4); - // assertTrue(poly1.isClosedPath(0)); - // assertTrue(poly1.isClosedPath(1)); - // assertTrue(poly1.getPointCount() == 11); - // assertTrue(poly1.getPathSize(1) == 7); - // Point2D ptOut; - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 314 && ptOut.y == 217); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 300 && ptOut.y == 147); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); } } diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/unittest/com/esri/core/geometry/TestSimplify.java index f39ae4de..8d76da1b 100644 --- a/unittest/com/esri/core/geometry/TestSimplify.java +++ b/unittest/com/esri/core/geometry/TestSimplify.java @@ -431,9 +431,6 @@ public void testPolygon5() { true, null, null); assertTrue(res); - // FIXME Bowtie. once simplify is fixed this should result in a - // simplified geom - int pointCount = simplePolygon5.getPointCount(); assertTrue(pointCount == 6); From 96c5c608ff8f61c6a366d48f1022c7d0f7b42b3e Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Thu, 12 Sep 2013 14:20:16 -0700 Subject: [PATCH 002/145] Updates to contribution statements; link contribution site --- CONTRIBUTING.md | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..4180c704 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). diff --git a/README.md b/README.md index ca143e5f..618f1572 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a ## Contributing -Anyone and everyone is welcome to contribute. +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing Copyright 2013 Esri From 100dadfbbbfbaf5aa5a953c5294322852e47c89e Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 19 Sep 2013 15:40:08 -0700 Subject: [PATCH 003/145] Fixed OGCMultiPolygon.geometryN, added OGCGeometry.convertToMulti --- .../ogc/OGCConcreteGeometryCollection.java | 6 +++ .../esri/core/geometry/ogc/OGCGeometry.java | 6 +++ .../esri/core/geometry/ogc/OGCLineString.java | 6 +++ .../core/geometry/ogc/OGCMultiLineString.java | 6 +++ .../esri/core/geometry/ogc/OGCMultiPoint.java | 6 +++ .../core/geometry/ogc/OGCMultiPolygon.java | 8 ++- src/com/esri/core/geometry/ogc/OGCPoint.java | 6 +++ .../esri/core/geometry/ogc/OGCPolygon.java | 6 +++ unittest/com/esri/core/geometry/TestOGC.java | 49 ++++++++++++++----- 9 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index a3b6d989..a111cf23 100644 --- a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -305,4 +305,10 @@ public void setSpatialReference(SpatialReference esriSR_) { } } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + } diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/com/esri/core/geometry/ogc/OGCGeometry.java index 47c5c304..ef6e1452 100644 --- a/src/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/com/esri/core/geometry/ogc/OGCGeometry.java @@ -634,4 +634,10 @@ protected boolean isConcreteGeometryCollection() { public void setSpatialReference(SpatialReference esriSR_) { esriSR = esriSR_; } + + /** + *Converts this Geometry to the OGCMulti* if it is not OGCMulti* or OGCGeometryCollection already. + * @return OGCMulti* or OGCGeometryCollection instance. + */ + public abstract OGCGeometry convertToMulti(); } diff --git a/src/com/esri/core/geometry/ogc/OGCLineString.java b/src/com/esri/core/geometry/ogc/OGCLineString.java index 6310a77f..eb3ee7bb 100644 --- a/src/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/com/esri/core/geometry/ogc/OGCLineString.java @@ -107,5 +107,11 @@ public Geometry getEsriGeometry() { return multiPath; } + @Override + public OGCGeometry convertToMulti() + { + return new OGCMultiLineString((Polyline)multiPath, esriSR); + } + MultiPath multiPath; } diff --git a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java index 2183020d..2ea784a3 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -76,5 +76,11 @@ public Geometry getEsriGeometry() { return polyline; } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + Polyline polyline; } diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/com/esri/core/geometry/ogc/OGCMultiPoint.java index fa3f7c83..a57c3b4e 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -95,5 +95,11 @@ public Geometry getEsriGeometry() { return multiPoint; } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + private com.esri.core.geometry.MultiPoint multiPoint; } diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java index ac72df56..878ea168 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -52,7 +52,7 @@ public OGCGeometry geometryN(int n) { if (polygon.isExteriorRing(i)) exterior++; - if (exterior == i + 1) { + if (exterior == n + 1) { return new OGCPolygon(polygon, i, esriSR); } } @@ -90,5 +90,11 @@ public Geometry getEsriGeometry() { return polygon; } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + Polygon polygon; } diff --git a/src/com/esri/core/geometry/ogc/OGCPoint.java b/src/com/esri/core/geometry/ogc/OGCPoint.java index d04bbc62..b6a8f9e0 100644 --- a/src/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/com/esri/core/geometry/ogc/OGCPoint.java @@ -76,6 +76,12 @@ public OGCGeometry locateBetween(double mStart, double mEnd) { public com.esri.core.geometry.Geometry getEsriGeometry() { return point; } + + @Override + public OGCGeometry convertToMulti() + { + return new OGCMultiPoint(point, esriSR); + } com.esri.core.geometry.Point point; diff --git a/src/com/esri/core/geometry/ogc/OGCPolygon.java b/src/com/esri/core/geometry/ogc/OGCPolygon.java index e3613e3a..c9f9fcff 100644 --- a/src/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/com/esri/core/geometry/ogc/OGCPolygon.java @@ -102,5 +102,11 @@ public Geometry getEsriGeometry() { return polygon; } + @Override + public OGCGeometry convertToMulti() + { + return new OGCMultiPolygon(polygon, esriSR); + } + Polygon polygon; } diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index 218ebdc3..3099767a 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -150,6 +150,8 @@ public void testFirstPointOfPolygon() { assertTrue(ls.pointN(3).equals(OGCGeometry.fromText("POINT(-10 10)"))); OGCPoint p0 = ls.pointN(0); assertTrue(ls.pointN(0).equals(OGCGeometry.fromText("POINT(-10 -10)"))); + String ms = g.convertToMulti().asText(); + assertTrue(ms.equals("MULTIPOLYGON (((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))")); } @@ -161,6 +163,8 @@ public void testFirstPointOfLineString() { assertTrue(p.numPoints() == 5); assertTrue(p.isClosed()); assertTrue(p.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + String ms = g.convertToMulti().asText(); + assertTrue(ms.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))")); } public void testPointInPolygon() { @@ -173,20 +177,39 @@ public void testPointInPolygon() { assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); - } public void testMultiPolygon() { - OGCGeometry g = OGCGeometry - .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); - assertTrue(g.geometryType().equals("MultiPolygon")); // the type is - // reduced - assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + // reduced + assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.convertToMulti() == g); + } + + { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)), ((90 90, 110 90, 110 110, 90 110, 90 90), (95 95, 95 105, 105 105, 105 95, 95 95)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + + OGCMultiPolygon mp = (OGCMultiPolygon)g; + assertTrue(mp.numGeometries() == 2); + OGCGeometry p1 = mp.geometryN(0); + assertTrue(p1.geometryType().equals("Polygon")); // the type is + assertTrue(p1.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!p1.contains(OGCGeometry.fromText("POINT(109 109)"))); + OGCGeometry p2 = mp.geometryN(1); + assertTrue(p2.geometryType().equals("Polygon")); // the type is + assertTrue(!p2.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(p2.contains(OGCGeometry.fromText("POINT(109 109)"))); + } } public void testMultiPolygonUnion() { @@ -728,6 +751,10 @@ public void testMultiPointSinglePoint() { OGCGeometry p = mp.geometryN(0); String s = p.asText(); assertTrue(s.equals("POINT (1 0)")); + + String ms = p.convertToMulti().asText(); + assertTrue(ms.equals("MULTIPOINT ((1 0))")); + } public void testWktMultiPolygon() { From 8b3be68d801a46b285ec3b18ad348a0cdcb51a23 Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Fri, 18 Oct 2013 11:58:30 -0700 Subject: [PATCH 004/145] Added import from JSONObject and export to JsonWriter --- src/com/esri/core/geometry/JSONUtils.java | 23 +- src/com/esri/core/geometry/JsonReader.java | 368 +++++ src/com/esri/core/geometry/JsonWriter.java | 462 ++++++ .../core/geometry/OperatorExportToJson.java | 61 +- .../geometry/OperatorExportToJsonCursor.java | 835 ++++++----- .../geometry/OperatorExportToJsonLocal.java | 33 +- .../core/geometry/OperatorImportFromJson.java | 9 + .../OperatorImportFromJsonCursor.java | 57 +- .../geometry/OperatorImportFromJsonLocal.java | 10 + src/com/esri/core/geometry/QuadTreeImpl.java | 2 +- src/com/esri/core/geometry/StringUtils.java | 121 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 22 +- .../esri/core/geometry/TestJsonParser.java | 1301 +++++++++-------- .../com/esri/core/geometry/TestQuadTree.java | 13 + 14 files changed, 2130 insertions(+), 1187 deletions(-) create mode 100644 src/com/esri/core/geometry/JsonReader.java create mode 100644 src/com/esri/core/geometry/JsonWriter.java diff --git a/src/com/esri/core/geometry/JSONUtils.java b/src/com/esri/core/geometry/JSONUtils.java index e401df70..1ec51185 100644 --- a/src/com/esri/core/geometry/JSONUtils.java +++ b/src/com/esri/core/geometry/JSONUtils.java @@ -25,26 +25,25 @@ import java.io.IOException; import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; final class JSONUtils { - static boolean isObjectStart(JsonParser parser) throws Exception { - return parser.getCurrentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT - : parser.getCurrentToken() == JsonToken.START_OBJECT; + static boolean isObjectStart(JsonReader parser) throws Exception { + return parser.currentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT + : parser.currentToken() == JsonToken.START_OBJECT; } - static double readDouble(JsonParser parser) throws JsonParseException, + static double readDouble(JsonReader parser) throws JsonParseException, IOException, Exception { - if (parser.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) - return parser.getDoubleValue(); - else if (parser.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) - return parser.getIntValue(); - else if (parser.getCurrentToken() == JsonToken.VALUE_NULL) + if (parser.currentToken() == JsonToken.VALUE_NUMBER_FLOAT) + return parser.currentDoubleValue(); + else if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + return parser.currentIntValue(); + else if (parser.currentToken() == JsonToken.VALUE_NULL) return NumberUtils.NaN(); - else if (parser.getCurrentToken() == JsonToken.VALUE_STRING) - if (parser.getText().equals("NaN")) + else if (parser.currentToken() == JsonToken.VALUE_STRING) + if (parser.currentString().equals("NaN")) return NumberUtils.NaN(); throw new GeometryException("invalid parameter"); diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java new file mode 100644 index 00000000..065ecafe --- /dev/null +++ b/src/com/esri/core/geometry/JsonReader.java @@ -0,0 +1,368 @@ +/* + Copyright 1995-2013 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; + + +abstract class JsonReader { + + abstract JsonToken nextToken() throws Exception; + + abstract JsonToken currentToken() throws Exception; + + abstract void skipChildren() throws Exception; + + abstract String currentString() throws Exception; + + abstract double currentDoubleValue() throws Exception; + + abstract int currentIntValue() throws Exception; +} +final class JsonParserReader extends JsonReader { + + private JsonParser m_jsonParser; + + JsonParserReader(JsonParser jsonParser) { + m_jsonParser = jsonParser; + } + + @Override + JsonToken nextToken() throws Exception { + JsonToken token = m_jsonParser.nextToken(); + return token; + } + + @Override + JsonToken currentToken() throws Exception { + return m_jsonParser.getCurrentToken(); + } + + @Override + void skipChildren() throws Exception { + m_jsonParser.skipChildren(); + } + + @Override + String currentString() throws Exception { + return m_jsonParser.getText(); + } + + @Override + double currentDoubleValue() throws Exception { + return m_jsonParser.getValueAsDouble(); + } + + @Override + int currentIntValue() throws Exception { + return m_jsonParser.getValueAsInt(); + } +} + +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 Exception { + 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 Exception { + return m_currentToken; + } + + @Override + void skipChildren() throws Exception { + 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 Exception { + 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 Exception { + 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 Exception { + if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { + throw new GeometryException("invalid call"); + } + + return ((Number) currentObject_()).intValue(); + } +} + +final class JSONObjectEnumerator { + + private JSONObject m_jsonObject; + private boolean m_bStarted; + private int m_currentIndex; + private String[] m_keys; + + JSONObjectEnumerator(JSONObject jsonObject) { + m_bStarted = false; + m_currentIndex = -1; + m_jsonObject = jsonObject; + } + + String getCurrentKey() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_keys[m_currentIndex]; + } + + Object getCurrentObject() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_jsonObject.get(m_keys[m_currentIndex]); + } + + boolean next() { + if (!m_bStarted) { + m_currentIndex = 0; + m_keys = JSONObject.getNames(m_jsonObject); + m_bStarted = true; + } else if (m_currentIndex != m_jsonObject.length()) { + m_currentIndex++; + } + + return m_currentIndex != m_jsonObject.length(); + } +} + +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.get(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/com/esri/core/geometry/JsonWriter.java b/src/com/esri/core/geometry/JsonWriter.java new file mode 100644 index 00000000..710a34d6 --- /dev/null +++ b/src/com/esri/core/geometry/JsonWriter.java @@ -0,0 +1,462 @@ +/* + Copyright 1995-2013 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; + +abstract class JsonWriter { + + abstract Object getJson(); + + abstract void startObject(); + + abstract void startArray(); + + abstract void endObject(); + + abstract void endArray(); + + abstract void addPairObject(String fieldName); + + abstract void addPairArray(String fieldName); + + abstract void addPairString(String fieldName, String v); + + abstract void addPairDouble(String fieldName, double v); + + abstract void addPairDoubleF(String fieldName, double v, int decimals); + + abstract void addPairInt(String fieldName, int v); + + abstract void addPairBoolean(String fieldName, boolean v); + + abstract void addPairNull(String fieldName); + + abstract void addValueObject(); + + abstract void addValueArray(); + + abstract void addValueString(String v); + + abstract void addValueDouble(double v); + + abstract void addValueDoubleF(double v, int decimals); + + abstract void addValueInt(int v); + + abstract void addValueBoolean(boolean v); + + abstract void addValueNull(); + + 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; + } + + 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; + } +} + +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 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) { + 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('"'); + } +} diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/com/esri/core/geometry/OperatorExportToJson.java index 8bf1240e..60729155 100644 --- a/src/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/com/esri/core/geometry/OperatorExportToJson.java @@ -23,35 +23,46 @@ */ package com.esri.core.geometry; +import java.util.Map; + import com.esri.core.geometry.Operator.Type; /** - *Export to JSON format. + * Export to JSON format. */ public abstract class OperatorExportToJson extends Operator { - @Override - public Type getType() { - return Type.ExportToJson; - } - - /** - * Performs the ExportToJson operation - * - * @return Returns a JsonCursor. - */ - abstract JsonCursor execute(SpatialReference spatialReference, - GeometryCursor geometryCursor); - - /** - *Performs the ExportToJson operation - *@return Returns a String. - */ - public abstract String execute(SpatialReference spatialReference, - Geometry geometry); - - public static OperatorExportToJson local() { - return (OperatorExportToJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ExportToJson); - } + @Override + public Type getType() { + return Type.ExportToJson; + } + + /** + * Performs the ExportToJson operation + * + * @return Returns a JsonCursor. + */ + abstract JsonCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor); + + /** + * Performs the ExportToJson operation + * + * @return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry); + + /** + * Performs the ExportToJson operation + * + * @return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry, Map exportProperties); + + public static OperatorExportToJson local() { + return (OperatorExportToJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToJson); + } } diff --git a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToJsonCursor.java index 6de353f1..85547549 100644 --- a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -25,408 +25,441 @@ 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; +import java.util.Map; class OperatorExportToJsonCursor extends JsonCursor { - GeometryCursor m_inputGeometryCursor; - int m_index; - int m_wkid = -1; - int m_latest_wkid = -1; - String m_wkt = null; - - private static JsonFactory factory = new JsonFactory(); - - public OperatorExportToJsonCursor(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; - } - - @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 exportToJson(geometry); - } - return null; - } - - private String exportToJson(Geometry geometry) { - StringWriter sw = new StringWriter(); - try { - JsonGenerator gen = factory.createJsonGenerator(sw); - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Point: - exportPointToJson(gen, (Point) geometry); - break; - - case Geometry.GeometryType.MultiPoint: - exportMultiPointToJson(gen, (MultiPoint) geometry); - break; - - case Geometry.GeometryType.Polyline: - exportPolylineToJson(gen, (Polyline) geometry); - break; - - case Geometry.GeometryType.Polygon: - exportPolygonToJson(gen, (Polygon) geometry); - break; - - case Geometry.GeometryType.Envelope: - exportEnvelopeToJson(gen, (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 exportPolygonToJson(JsonGenerator g, Polygon pp) - throws JsonGenerationException, IOException { - exportPolypathToJson(g, pp, "rings"); - } - - private void exportPolylineToJson(JsonGenerator g, Polyline pp) - throws JsonGenerationException, IOException { - exportPolypathToJson(g, pp, "paths"); - } - - private void exportPolypathToJson(JsonGenerator g, MultiPath pp, String name) - throws JsonGenerationException, IOException { - boolean bExportZs = pp.hasAttribute(Semantics.Z); - boolean bExportMs = pp.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (bExportZs) { - g.writeFieldName("hasZ"); - g.writeBoolean(true); - } - - if (bExportMs) { - g.writeFieldName("hasM"); - g.writeBoolean(true); - } - - g.writeFieldName(name); - - g.writeStartArray(); - - 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++) { - g.writeStartArray(); - int startindex = pp.getPathStart(i); - int numVertices = pp.getPathSize(i); - for (int j = startindex; j < startindex + numVertices; j++) { - pp.getXY(j, pt); - - g.writeStartArray(); - - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (bExportZs) { - double z = zs.get(j); - writeDouble(z, g); - } - - if (bExportMs) { - double m = ms.get(j); - writeDouble(m, g); - } - - g.writeEndArray(); - } - - // Close the Path/Ring by writing the Point at the start index - if (bPolygon) { - pp.getXY(startindex, pt); - // getPoint(startindex); - g.writeStartArray(); - - g.writeNumber(pt.x); - g.writeNumber(pt.y); - - if (bExportZs) { - double z = zs.get(startindex); - writeDouble(z, g); - } - - if (bExportMs) { - double m = ms.get(startindex); - writeDouble(m, g); - } - - g.writeEndArray(); - } - - g.writeEndArray(); - } - } - - g.writeEndArray(); - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void exportMultiPointToJson(JsonGenerator g, MultiPoint mpt) - throws JsonGenerationException, IOException { - boolean bExportZs = mpt.hasAttribute(Semantics.Z); - boolean bExportMs = mpt.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (bExportZs) { - g.writeFieldName("hasZ"); - g.writeBoolean(true); - } - - if (bExportMs) { - g.writeFieldName("hasM"); - g.writeBoolean(true); - } - - g.writeFieldName("points"); - - g.writeStartArray(); - - 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); - - g.writeStartArray(); - - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (bExportZs) { - double z = zs.get(i); - writeDouble(z, g); - } - - if (bExportMs) { - double m = ms.get(i); - writeDouble(m, g); - } - - g.writeEndArray(); - } - } - - g.writeEndArray(); - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void exportPointToJson(JsonGenerator g, Point pt) - throws JsonGenerationException, IOException { - boolean bExportZs = pt.hasAttribute(Semantics.Z); - boolean bExportMs = pt.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (pt.isEmpty()) { - g.writeFieldName("x"); - g.writeNull(); - g.writeFieldName("y"); - g.writeNull(); - - if (bExportZs) { - g.writeFieldName("z"); - g.writeNull(); - } - - if (bExportMs) { - g.writeFieldName("m"); - g.writeNull(); - } - } else { - g.writeFieldName("x"); - writeDouble(pt.getX(), g); - g.writeFieldName("y"); - writeDouble(pt.getY(), g); - - if (bExportZs) { - g.writeFieldName("z"); - writeDouble(pt.getZ(), g); - } - - if (bExportMs) { - g.writeFieldName("m"); - writeDouble(pt.getM(), g); - } - } - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void exportEnvelopeToJson(JsonGenerator g, Envelope env) - throws JsonGenerationException, IOException { - boolean bExportZs = env.hasAttribute(Semantics.Z); - boolean bExportMs = env.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (env.isEmpty()) { - g.writeFieldName("xmin"); - g.writeNull(); - g.writeFieldName("ymin"); - g.writeNull(); - g.writeFieldName("xmax"); - g.writeNull(); - g.writeFieldName("ymax"); - g.writeNull(); - - if (bExportZs) { - g.writeFieldName("zmin"); - g.writeNull(); - g.writeFieldName("zmax"); - g.writeNull(); - } - - if (bExportMs) { - g.writeFieldName("mmin"); - g.writeNull(); - g.writeFieldName("mmax"); - g.writeNull(); - } - } else { - g.writeFieldName("xmin"); - writeDouble(env.getXMin(), g); - g.writeFieldName("ymin"); - writeDouble(env.getYMin(), g); - g.writeFieldName("xmax"); - writeDouble(env.getXMax(), g); - g.writeFieldName("ymax"); - writeDouble(env.getYMax(), g); - - if (bExportZs) { - Envelope1D z = env.queryInterval(Semantics.Z, 0); - g.writeFieldName("zmin"); - writeDouble(z.vmin, g); - g.writeFieldName("zmax"); - writeDouble(z.vmax, g); - } - - if (bExportMs) { - Envelope1D m = env.queryInterval(Semantics.M, 0); - g.writeFieldName("mmin"); - writeDouble(m.vmin, g); - g.writeFieldName("mmax"); - writeDouble(m.vmax, g); - } - } - - writeSR(g); - - 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; - } - - private void writeSR(JsonGenerator g) throws IOException, - JsonGenerationException { - if (m_wkid > 0) { - g.writeFieldName("spatialReference"); - g.writeStartObject(); - - g.writeFieldName("wkid"); - g.writeNumber(m_wkid); - - if (m_latest_wkid > 0 && m_latest_wkid != m_wkid) { - g.writeFieldName("latestWkid"); - g.writeNumber(m_latest_wkid); - } - - g.writeEndObject(); - } else if (m_wkt != null) { - g.writeFieldName("spatialReference"); - g.writeStartObject(); - g.writeFieldName("wkt"); - g.writeString(m_wkt); - g.writeEndObject(); - } else - return; - } + 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(); + } + } + } } diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java index 32586c5f..8b93cb1b 100644 --- a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -23,19 +23,26 @@ */ package com.esri.core.geometry; -class OperatorExportToJsonLocal extends OperatorExportToJson { - - @Override - JsonCursor execute(SpatialReference spatialReference, - GeometryCursor geometryCursor) { - return new OperatorExportToJsonCursor(spatialReference, geometryCursor); - } +import java.util.Map; - @Override - public String execute(SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); - return cursor.next(); - } +class OperatorExportToJsonLocal extends OperatorExportToJson { + @Override + JsonCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor) { + return new OperatorExportToJsonCursor(spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + JsonCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); + return cursor.next(); + } + + @Override + public String execute(SpatialReference spatialReference, + Geometry geometry, Map exportProperties) { + return OperatorExportToJsonCursor.exportToString(geometry, spatialReference, exportProperties); + } } diff --git a/src/com/esri/core/geometry/OperatorImportFromJson.java b/src/com/esri/core/geometry/OperatorImportFromJson.java index 66e59b9b..c05438eb 100644 --- a/src/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/com/esri/core/geometry/OperatorImportFromJson.java @@ -28,6 +28,8 @@ 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; @@ -62,6 +64,13 @@ public abstract MapGeometry execute(Geometry.Type type, 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 static OperatorImportFromJson local() { return (OperatorImportFromJson) OperatorFactoryLocal.getInstance() diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java index ac982316..cc56169b 100644 --- a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -26,7 +26,6 @@ import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; import com.esri.core.geometry.VertexDescription.Semantics; -import java.io.IOException; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; @@ -57,12 +56,12 @@ public MapGeometry next() { JsonParser jsonParser; if ((jsonParser = m_inputJsonParsers.next()) != null) { m_index = m_inputJsonParsers.getID(); - return importFromJsonParser(m_type, jsonParser); + return importFromJsonParser(m_type, new JsonParserReader(jsonParser)); } return null; } - private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { + static MapGeometry importFromJsonParser(int gt, JsonReader parser) { MapGeometry mp; try { @@ -110,25 +109,25 @@ private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { SpatialReference spatial_reference = null; while (parser.nextToken() != JsonToken.END_OBJECT) { - String name = parser.getCurrentName(); + String name = parser.currentString(); parser.nextToken(); if (!bFoundSpatial_reference && name.equals("spatialReference")) { bFoundSpatial_reference = true; - if (parser.getCurrentToken() == JsonToken.START_OBJECT) { + if (parser.currentToken() == JsonToken.START_OBJECT) { spatial_reference = SpatialReference.fromJson(parser); } else { - if (parser.getCurrentToken() != JsonToken.VALUE_NULL) + if (parser.currentToken() != JsonToken.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.getCurrentToken() == JsonToken.VALUE_TRUE); + bHasZ = (parser.currentToken() == JsonToken.VALUE_TRUE); } else if (!bFoundHasM && name.equals("hasM")) { bFoundHasM = true; - bHasM = (parser.getCurrentToken() == JsonToken.VALUE_TRUE); + bHasM = (parser.currentToken() == JsonToken.VALUE_TRUE); } else if (!bFoundPolygon && name.equals("rings") && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { @@ -279,55 +278,55 @@ private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { return mp; } - public static MapGeometry fromJsonToUnknown(JsonParser parser) + public static MapGeometry fromJsonToUnknown(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Unknown, parser); } - public static MapGeometry fromJsonToEnvelope(JsonParser parser) + public static MapGeometry fromJsonToEnvelope(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Envelope, parser); } - public static MapGeometry fromJsonToPoint(JsonParser parser) + public static MapGeometry fromJsonToPoint(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Point, parser); } - public static MapGeometry fromJsonToPolygon(JsonParser parser) + public static MapGeometry fromJsonToPolygon(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Polygon, parser); } - public static MapGeometry fromJsonToPolyline(JsonParser parser) + public static MapGeometry fromJsonToPolyline(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Polyline, parser); } - public static MapGeometry fromJsonToMultiPoint(JsonParser parser) + public static MapGeometry fromJsonToMultiPoint(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); } - private static void windup(JsonParser parser) throws IOException, + private static void windup(JsonReader parser) throws Exception, JsonParseException { parser.skipChildren(); } - private static double readDouble(JsonParser parser) throws IOException, + private static double readDouble(JsonReader parser) throws Exception, JsonParseException { - if (parser.getCurrentToken() == JsonToken.VALUE_NULL - || parser.getCurrentToken() == JsonToken.VALUE_STRING - && parser.getCurrentName().equals("NaN")) + if (parser.currentToken() == JsonToken.VALUE_NULL + || parser.currentToken() == JsonToken.VALUE_STRING + && parser.currentString().equals("NaN")) return NumberUtils.NaN(); else - return parser.getValueAsDouble(); + return parser.currentDoubleValue(); } - private static Geometry importFromJsonMultiPoint(JsonParser parser, + private static Geometry importFromJsonMultiPoint(JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array of vertices is expected"); @@ -343,7 +342,7 @@ private static Geometry importFromJsonMultiPoint(JsonParser parser, int sz; double[] buf = new double[4]; while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); @@ -408,9 +407,9 @@ else if (c < 16) } private static Geometry importFromJsonMultiPath(boolean b_polygon, - JsonParser parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) + JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipath: array of array of vertices is expected"); @@ -439,7 +438,7 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, // At start of rings while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipath: ring/path array is expected"); @@ -449,8 +448,8 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, int szstart = 0; parser.nextToken(); - while (parser.getCurrentToken() != JsonToken.END_ARRAY) { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + while (parser.currentToken() != JsonToken.END_ARRAY) { + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); @@ -524,7 +523,7 @@ else if (c < 16) point_count++; pathPointCount++; } while (pathPointCount < requiredSize - && parser.getCurrentToken() == JsonToken.END_ARRAY); + && parser.currentToken() == JsonToken.END_ARRAY); } if (b_polygon && pathPointCount > requiredSize && sz == szstart diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java index da614dfb..f92842fb 100644 --- a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -28,6 +28,8 @@ 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; @@ -55,4 +57,12 @@ public MapGeometry execute(Geometry.Type type, String string) jsonParserPt.nextToken(); return execute(type, jsonParserPt); } + @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/com/esri/core/geometry/QuadTreeImpl.java b/src/com/esri/core/geometry/QuadTreeImpl.java index e1035b6b..8a927447 100644 --- a/src/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/com/esri/core/geometry/QuadTreeImpl.java @@ -44,7 +44,7 @@ void resetIterator(Geometry query, double tolerance) { if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { int type = query.getType().value(); - m_b_linear = Geometry.isLinear(type); + m_b_linear = Geometry.isSegment(type); if (m_b_linear) { Segment segment = (Segment) query; diff --git a/src/com/esri/core/geometry/StringUtils.java b/src/com/esri/core/geometry/StringUtils.java index c9267b5c..6797d94e 100644 --- a/src/com/esri/core/geometry/StringUtils.java +++ b/src/com/esri/core/geometry/StringUtils.java @@ -24,45 +24,86 @@ package com.esri.core.geometry; class StringUtils { - static void appendDouble(double value, int precision, - StringBuilder stringBuilder) { - if (precision < 0) - precision = 0; - else if (precision > 17) - precision = 17; - - String format = "%." + precision + "g"; - - String str_dbl = String.format(format, value); - - boolean b_found_dot = false; - boolean b_found_exponent = false; - - for (int i = 0; i < str_dbl.length(); i++) { - char c = str_dbl.charAt(i); - - if (c == '.') - b_found_dot = true; - else if (c == 'e' || c == 'E') - b_found_exponent = true; - } - - if (b_found_dot && !b_found_exponent) { - StringBuilder buffer = new StringBuilder(str_dbl); - int non_zero = buffer.length() - 1; - - while (buffer.charAt(non_zero) == '0') - non_zero--; - - buffer.delete(non_zero + 1, buffer.length()); - - if (buffer.charAt(non_zero) == '.') - buffer.deleteCharAt(non_zero); - - stringBuilder.append(buffer); - } else { - stringBuilder.append(str_dbl); - } - } + static void appendDouble(double value, int precision, + StringBuilder stringBuilder) { + if (precision < 0) { + precision = 0; + } else if (precision > 17) { + precision = 17; + } + + String format = "%." + precision + "g"; + + String str_dbl = String.format(format, value); + + boolean b_found_dot = false; + boolean b_found_exponent = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') { + b_found_dot = true; + } else if (c == 'e' || c == 'E') { + b_found_exponent = true; + break; + } + } + + if (b_found_dot && !b_found_exponent) { + StringBuilder buffer = removeTrailingZeros_(str_dbl); + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + + static void appendDoubleF(double value, int decimals, + StringBuilder stringBuilder) { + if (decimals < 0) { + decimals = 0; + } else if (decimals > 17) { + decimals = 17; + } + + String format = "%." + decimals + "f"; + + String str_dbl = String.format(format, value); + + boolean b_found_dot = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') { + b_found_dot = true; + break; + } + } + + if (b_found_dot) { + StringBuilder buffer = removeTrailingZeros_(str_dbl); + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + + static private StringBuilder removeTrailingZeros_(String str_dbl) { + StringBuilder buffer = new StringBuilder(str_dbl); + int non_zero = buffer.length() - 1; + + while (buffer.charAt(non_zero) == '0') { + non_zero--; + } + + buffer.delete(non_zero + 1, buffer.length()); + + if (buffer.charAt(non_zero) == '.') { + buffer.deleteCharAt(non_zero); + } + + return buffer; + } } diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 90277da9..8d32bbe2 100644 --- a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -76,12 +76,6 @@ boolean testPoint() throws JsonParseException, IOException { String pointEmptyString = GeometryEngine.geometryToJson( spatialReferenceWebMerc1, pointEmpty); pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - // FIXME - // pointWebMerc1MP = - // GeometryEngine.jsonToGeometry(pointWebMerc1Parser); - // assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - // assertTrue(spatialReferenceWebMerc1.getID() == - // pointWebMerc1MP.getSpatialReference().getID()); } JsonParser pointWebMerc2Parser = factory @@ -138,7 +132,7 @@ boolean testPoint() throws JsonParseException, IOException { String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"x\":10.0,\"y\":20.0,\"z\":30.0,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } {// import @@ -198,12 +192,6 @@ boolean testMultiPoint() throws JsonParseException, IOException { bAnswer = false; } - // FIXME - // MultiPoint mPointEmpty = new MultiPoint(); - // String mPointEmptyString = - // GeometryEngine.geometryToJson(spatialReferenceWGS84, - // mPointEmpty); - // mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); } { @@ -219,7 +207,7 @@ boolean testMultiPoint() throws JsonParseException, IOException { p.add(20.0, 40.0, 60.0); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10.0,20.0,30.0,null],[20.0,40.0,60.0,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .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}}"; @@ -315,7 +303,7 @@ boolean testPolyline() throws JsonParseException, IOException { p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,5.0]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .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}}")); } { @@ -412,7 +400,7 @@ boolean testPolygon() throws JsonParseException, IOException { p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,7.0],[4.0,4.0,0.0,5.0],[0.0,0.0,3.0,null]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null],[7.0,8.0,0.0,5.0],[2.0,2.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .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}}")); } { @@ -484,7 +472,7 @@ boolean testEnvelope() throws JsonParseException, IOException { e.setInterval(VertexDescription.Semantics.M, 0, m); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); assertTrue(s - .equals("{\"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}}")); + .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } {// import diff --git a/unittest/com/esri/core/geometry/TestJsonParser.java b/unittest/com/esri/core/geometry/TestJsonParser.java index 9c22d49a..de188f3e 100644 --- a/unittest/com/esri/core/geometry/TestJsonParser.java +++ b/unittest/com/esri/core/geometry/TestJsonParser.java @@ -1,663 +1,666 @@ package com.esri.core.geometry; +import java.util.Hashtable; 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; 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.createJsonParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - // FIXME add 3D support - // assertTrue(10.0 == ((Point)point3DMP.getGeometry()).getZ()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } + + 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.createJsonParser(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); - // FIXME - 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); - // System.out.println("\n\nWKID: "+ - // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - System.out.print(ex.getMessage()); - } - } - - @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.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"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.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"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); - } + @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); + // System.out.println("\n\nWKID: "+ + // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + System.out.print(ex.getMessage()); + } + } + + @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/unittest/com/esri/core/geometry/TestQuadTree.java b/unittest/com/esri/core/geometry/TestQuadTree.java index adca5ac6..3836ec13 100644 --- a/unittest/com/esri/core/geometry/TestQuadTree.java +++ b/unittest/com/esri/core/geometry/TestQuadTree.java @@ -34,6 +34,19 @@ 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); + + element_handle = qtIter.next(); + while (element_handle > 0) { + int index = quadtree.getElement(element_handle); + assertTrue(index == 6 || index == 8 || index == 14); + element_handle = qtIter.next(); + } } @Test From 7fd76dcca8c318096ef0d6c22b82821eb420726b Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 18 Oct 2013 12:16:21 -0700 Subject: [PATCH 005/145] Made some methods public (Point2D, JsonCursor). Bug fixes for internal tests. Added a new unit test. Need Point2D public. Need JsonCursor public. Found bugs with handling of the attributes (Zs and Ms). Added proper unit test. Found a bug in Clipper. --- src/com/esri/core/geometry/Clipper.java | 216 ++++++------ src/com/esri/core/geometry/Envelope.java | 167 ++++----- src/com/esri/core/geometry/Envelope2D.java | 24 +- src/com/esri/core/geometry/Geometry.java | 78 +---- src/com/esri/core/geometry/JsonCursor.java | 2 +- .../core/geometry/MultiVertexGeometry.java | 13 +- .../geometry/MultiVertexGeometryImpl.java | 62 +--- .../geometry/OperatorExportToGeoJson.java | 2 +- .../OperatorExportToGeoJsonLocal.java | 2 +- .../core/geometry/OperatorExportToJson.java | 2 +- .../geometry/OperatorExportToJsonLocal.java | 2 +- .../geometry/OperatorIntersectionCursor.java | 13 +- src/com/esri/core/geometry/Point.java | 73 ++-- src/com/esri/core/geometry/Point2D.java | 45 +-- src/com/esri/core/geometry/Segment.java | 113 +++--- .../esri/core/geometry/SpatialReference.java | 96 ++++-- .../VertexDescriptionDesignerImpl.java | 51 +++ .../esri/core/geometry/TestAttributes.java | 324 ++++++++++++++++++ 18 files changed, 796 insertions(+), 489 deletions(-) create mode 100644 unittest/com/esri/core/geometry/TestAttributes.java diff --git a/src/com/esri/core/geometry/Clipper.java b/src/com/esri/core/geometry/Clipper.java index f0eea524..fd064c95 100644 --- a/src/com/esri/core/geometry/Clipper.java +++ b/src/com/esri/core/geometry/Clipper.java @@ -592,7 +592,7 @@ void densifyAlongClipExtent_(double densify_dist) { } while (vertex != first_vertex); } } - + void splitSegments_(boolean b_axis_x, double clip_value) { // After the clipping, we could have produced unwanted segment overlaps // along the clipping envelope boundary. @@ -630,12 +630,8 @@ void splitSegments_(boolean b_axis_x, double clip_value) { return; } - // SORTDYNAMICARRAYEX(&sorted_vertices, int, 0, sorted_vertices.size(), - // Clipper_vertex_comparer, this); sorted_vertices.Sort(0, sorted_vertices.size(), new ClipperVertexComparer(this)); - // std::sort(sorted_vertices.get_ptr(), sorted_vertices.get_ptr() + - // sorted_vertices.size(), Clipper_vertex_comparer(this)); Point2D pt_tmp = new Point2D(); // forward declare for java port // optimization @@ -653,120 +649,124 @@ void splitSegments_(boolean b_axis_x, double clip_value) { int vert = sorted_vertices.get(index); m_shape.getXY(vert, pt); if (!pt.isEqual(pt_0)) { - if (index_0 != -1) { - // add new intervals, that started at pt_0 - for (int i = index_0; i < index; i++) { - int v = sorted_vertices.get(i); - int nextv = m_shape.getNextVertex(v); - int prevv = m_shape.getPrevVertex(v); - boolean bAdded = false; - if (compareVertices_(v, nextv) < 0) { - m_shape.getXY(nextv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) { - active_intervals.add(v); - bAdded = true; - m_shape.setUserIndex(v, node2, 1); - } + if (index_0 == -1) { + index_0 = index; + pt_0.setCoords(pt); + continue; + } + + // add new intervals, that started at pt_0 + for (int i = index_0; i < index; i++) { + int v = sorted_vertices.get(i); + int nextv = m_shape.getNextVertex(v); + int prevv = m_shape.getPrevVertex(v); + boolean bAdded = false; + if (compareVertices_(v, nextv) < 0) { + m_shape.getXY(nextv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + active_intervals.add(v); + bAdded = true; + m_shape.setUserIndex(v, node2, 1); } - if (compareVertices_(v, prevv) < 0) { - m_shape.getXY(prevv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) { - if (!bAdded) - active_intervals.add(v); - m_shape.setUserIndex(v, node1, 1); - } + } + if (compareVertices_(v, prevv) < 0) { + m_shape.getXY(prevv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + if (!bAdded) + active_intervals.add(v); + m_shape.setUserIndex(v, node1, 1); } } + } - // Split all active intervals at new point - for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { - int v = active_intervals.get(ia); - int n_1 = m_shape.getUserIndex(v, node1); - int n_2 = m_shape.getUserIndex(v, node2); - if (n_1 == 1) { - int prevv = m_shape.getPrevVertex(v); - m_shape.getXY(prevv, pt_1); - double[] t = new double[1]; - t[0] = 0; - if (!pt_1.isEqual(pt)) {// Split the active segment - double active_segment_length = Point2D - .distance(pt_0, pt_1); - t[0] = Point2D.distance(pt_1, pt) - / active_segment_length; - assert (t[0] >= 0 && t[0] <= 1.0); - if (t[0] == 0) - t[0] = NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - else if (t[0] == 1.0) { - t[0] = 1.0 - NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - assert (t[0] != 1.0); - } - - int split_count = m_shape.splitSegment(prevv, - t, 1); - assert (split_count > 0); - int v_1 = m_shape.getPrevVertex(v); - m_shape.setXY(v_1, pt); - new_active_intervals.add(v_1); - m_shape.setUserIndex(v_1, node1, 1); - m_shape.setUserIndex(v_1, node2, -1); - } else { - // The active segment ends at the current point. - // We skip it, and it goes away. + // Split all active intervals at new point + for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { + int v = active_intervals.get(ia); + int n_1 = m_shape.getUserIndex(v, node1); + int n_2 = m_shape.getUserIndex(v, node2); + if (n_1 == 1) { + int prevv = m_shape.getPrevVertex(v); + m_shape.getXY(prevv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) {// Split the active segment + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_1, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); } - } - if (n_2 == 1) { - int nextv = m_shape.getNextVertex(v); - m_shape.getXY(nextv, pt_1); - double[] t = new double[1]; - t[0] = 0; - if (!pt_1.isEqual(pt)) { - double active_segment_length = Point2D - .distance(pt_0, pt_1); - t[0] = Point2D.distance(pt_0, pt) - / active_segment_length; - assert (t[0] >= 0 && t[0] <= 1.0); - if (t[0] == 0) - t[0] = NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - else if (t[0] == 1.0) { - t[0] = 1.0 - NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - assert (t[0] != 1.0); - } - int split_count = m_shape.splitSegment(v, t, 1); - assert (split_count > 0); - int v_1 = m_shape.getNextVertex(v); - m_shape.setXY(v_1, pt); - new_active_intervals.add(v_1); - m_shape.setUserIndex(v_1, node1, -1); - m_shape.setUserIndex(v_1, node2, 1); - } + int split_count = m_shape.splitSegment(prevv, + t, 1); + assert (split_count > 0); + int v_1 = m_shape.getPrevVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, 1); + m_shape.setUserIndex(v_1, node2, -1); + } else { + // The active segment ends at the current point. + // We skip it, and it goes away. } } + if (n_2 == 1) { + int nextv = m_shape.getNextVertex(v); + m_shape.getXY(nextv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) { + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_0, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); + } - AttributeStreamOfInt32 tmp = active_intervals; - active_intervals = new_active_intervals; - new_active_intervals = tmp; - new_active_intervals.clear(false); + int split_count = m_shape.splitSegment(v, t, 1); + assert (split_count > 0); + int v_1 = m_shape.getNextVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, -1); + m_shape.setUserIndex(v_1, node2, 1); + } + } } + AttributeStreamOfInt32 tmp = active_intervals; + active_intervals = new_active_intervals; + new_active_intervals = tmp; + new_active_intervals.clear(false); + index_0 = index; pt_0.setCoords(pt); } diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index d9336182..b684cc23 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -37,7 +37,7 @@ public final class Envelope extends Geometry implements Serializable { Envelope2D m_envelope = new Envelope2D(); - double[] m_attributes;// use doubles to store everything (int64 are bitcast) + double[] m_attributes;// use doubles to store everything /** * Creates an envelope by defining its center, width, and height. @@ -410,9 +410,13 @@ public void copyTo(Geometry dst) { dst._touch(); envDst.m_description = m_description; envDst.m_envelope.setCoords(m_envelope); - envDst._resizeAttributes(m_description._getTotalComponents() - 2); - _attributeCopy(m_attributes, 0, envDst.m_attributes, 0, - (m_description._getTotalComponents() - 2) * 2); + envDst.m_attributes = null; + if (m_attributes != null) + { + envDst._ensureAttributes(); + System.arraycopy(m_attributes, 0, envDst.m_attributes, 0, + (m_description._getTotalComponents() - 2) * 2); + } } @Override @@ -597,6 +601,7 @@ 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) @@ -622,6 +627,8 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, else m_envelope.xmin = value; } + + return; } int ncomps = VertexDescription.getComponentCount(semantics); @@ -629,87 +636,88 @@ 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 + ordinate] = value; } - void _resizeAttributes(int newSize) {// copied from - // Segment::_ResizeAttributes + void _ensureAttributes() { _touch(); - if (m_attributes == null) { - m_attributes = new double[newSize * 2]; - } else if (m_attributes.length < newSize * 2) { - double[] newBuffer = new double[newSize * 2]; - System.arraycopy(m_attributes, 0, newBuffer, 0, m_attributes.length); - m_attributes = newBuffer; + if (m_attributes == null && m_description._getTotalComponents() > 2) { + m_attributes = new double[(m_description._getTotalComponents() - 2) * 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++; + } + } } } @Override - void _beforeDropAttributeImpl(int semantics) {// copied from - // Segment::_BeforeDropAttributeImpl - if (m_envelope.isEmpty()) + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; return; - - // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, - // POSITION)); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalCompsOld = m_description._getTotalComponents() - 2; - if (totalCompsOld > comps) { - int offset0 = _getEndPointOffset(0); - for (int i = offset + comps; i < totalCompsOld * 2; i++) - m_attributes[offset0 + i - comps] = m_attributes[offset0 + i]; - - int offset1 = _getEndPointOffset(1) - comps; // -comp is for deleted - // attribute of - // start vertex - for (int i = offset + comps; i < totalCompsOld; i++) - m_attributes[offset1 + i - comps] = m_attributes[offset1 + i]; - } - } - - @Override - void _afterAddAttributeImpl(int semantics) {// copied from - // Segment::_AfterAddAttributeImpl - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalComps = m_description._getTotalComponents() - 2; - _resizeAttributes(totalComps); - int totalCompsOld = totalComps - comps; // the total number of - // components before resize. - - int offset0 = _getEndPointOffset(0); - int offset1 = _getEndPointOffset(1); - int offset1old = offset1 - comps; - for (int i = totalCompsOld - 1; i >= 0; i--) {// correct the position of - // the End attributes - m_attributes[offset1 + i] = m_attributes[offset1old + i]; } - - // move attributes for start end end points that go after the insertion - // point - for (int i = totalComps - 1; i >= offset + comps; i--) { - m_attributes[offset0 + i] = m_attributes[offset0 + i - comps]; - m_attributes[offset1 + i] = m_attributes[offset1 + i - comps]; + + if (newDescription._getTotalComponents() > 2) { + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + + int old_offset0 = _getEndPointOffset(m_description, 0); + int old_offset1 = _getEndPointOffset(m_description, 1); + + int new_offset0 = _getEndPointOffset(newDescription, 0); + int new_offset1 = _getEndPointOffset(newDescription, 1); + + 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) + { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = d; + newAttributes[new_offset1 + j] = d; + j++; + } + } + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; + newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; + j++; + offset++; + } + } + + } + + m_attributes = newAttributes; } - - // initialize added attribute to the default value. - double dv = VertexDescription.getDefaultValue(semantics); - for (int i = 0; i < comps; i++) { - m_attributes[offset0 + offset + i] = dv; - m_attributes[offset1 + offset + i] = dv; + else { + m_attributes = null; } - } - - static void _attributeCopy(double[] src, int srcStart, double[] dst, - int dstStart, int count) { - if (count > 0) - System.arraycopy(src, srcStart, dst, dstStart, count); + + m_description = newDescription; } double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { @@ -733,10 +741,8 @@ static void _attributeCopy(double[] src, int srcStart, double[] dst, int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) { - if (null != m_attributes) - _resizeAttributes(m_description._getTotalComponents() - 2); - - return m_attributes[_getEndPointOffset(endPoint) + _ensureAttributes(); + return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; } else @@ -760,6 +766,8 @@ void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, else m_envelope.xmin = value; } + + return; } int ncomps = VertexDescription.getComponentCount(semantics); @@ -769,14 +777,13 @@ void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, if (!hasAttribute(semantics)) { if (VertexDescription.isDefaultValue(semantics, value)) return; + addAttribute(semantics); } int attributeIndex = m_description.getAttributeIndex(semantics); - if (null == m_attributes) - _resizeAttributes(m_description._getTotalComponents() - 2); - - m_attributes[_getEndPointOffset(endPoint) + _ensureAttributes(); + m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; } @@ -785,8 +792,8 @@ int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); } - int _getEndPointOffset(int endPoint) { - return endPoint * (m_description._getTotalComponents() - 2); + static int _getEndPointOffset(VertexDescription vd, int endPoint) { + return endPoint * (vd._getTotalComponents() - 2); } boolean isIntersecting(Envelope2D other) { diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index 2ea01b86..a3f09cf5 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -36,13 +36,13 @@ public final class Envelope2D { private final int XMASK = 3; private final int YMASK = 12; - double xmin; + public double xmin; - double ymin; + public double ymin; - double xmax; + public double xmax; - double ymax; + public double ymax; public static Envelope2D construct(double _xmin, double _ymin, double _xmax, double _ymax) { @@ -987,26 +987,25 @@ Point2D _snapClip(Point2D pt)// clips the point if it is outside, then snaps return new Point2D(x, y); } - boolean isPointOnBoundary(Point2D pt, double tolerance) { + public boolean isPointOnBoundary(Point2D pt, double tolerance) { return Math.abs(pt.x - xmin) <= tolerance || Math.abs(pt.x - xmax) <= tolerance || Math.abs(pt.y - ymin) <= tolerance || Math.abs(pt.y - ymax) <= tolerance; } - double distance(/* const */Envelope2D other) /* const */ + public double distance(/* const */Envelope2D other) { return Math.sqrt(sqrDistance(other)); } - double distance(/* const */Point2D pt2D) /* const */ + public double distance(Point2D pt2D) { return Math.sqrt(sqrDistance(pt2D)); } - double sqrDistance(/* const */Envelope2D other) /* const */ + public double sqrDistance(Envelope2D other) { - // code from SG's windist double dx = 0; double dy = 0; double nn; @@ -1030,9 +1029,8 @@ boolean isPointOnBoundary(Point2D pt, double tolerance) { return dx * dx + dy * dy; } - double sqrDistance(/* const */Point2D pt2D) /* const */ + public double sqrDistance(Point2D pt2D) { - // code from SG's windist double dx = 0; double dy = 0; double nn; @@ -1056,7 +1054,7 @@ boolean isPointOnBoundary(Point2D pt, double tolerance) { return dx * dx + dy * dy; } - void queryIntervalX(Envelope1D env1D) /* const */ + public void queryIntervalX(Envelope1D env1D) { if (isEmpty()) { env1D.setEmpty(); @@ -1065,7 +1063,7 @@ void queryIntervalX(Envelope1D env1D) /* const */ } } - void queryIntervalY(Envelope1D env1D) /* const */ + public void queryIntervalY(Envelope1D env1D) { if (isEmpty()) { env1D.setEmpty(); diff --git a/src/com/esri/core/geometry/Geometry.java b/src/com/esri/core/geometry/Geometry.java index 5684368d..0257579c 100644 --- a/src/com/esri/core/geometry/Geometry.java +++ b/src/com/esri/core/geometry/Geometry.java @@ -158,19 +158,10 @@ void assignVertexDescription(VertexDescription src) { if (src == m_description) return; - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (m_description.hasAttribute(i) && (!src.hasAttribute(i))) - _beforeDropAttributeImpl(i); - } - - VertexDescription olddescription = m_description; - m_description = src; - - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (!olddescription.hasAttribute(i) && src.hasAttribute(i)) - _afterAddAttributeImpl(i); - } + _assignVertexDescriptionImpl(src); } + + protected abstract void _assignVertexDescriptionImpl(VertexDescription src); /** * Merges the new VertexDescription by adding missing attributes from the @@ -183,30 +174,11 @@ void mergeVertexDescription(VertexDescription src) { return; // check if we need to do anything (if the src has same attributes) - boolean bNeedAction = false; - VertexDescriptionDesignerImpl vdd = null; - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (!m_description.hasAttribute(i) && src.hasAttribute(i)) { - if (!bNeedAction) { - bNeedAction = true; - vdd = new VertexDescriptionDesignerImpl(m_description); - } - - vdd.addAttribute(i); - } - } - - if (!bNeedAction) + VertexDescription newdescription = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, src); + if (newdescription == m_description) return; - - VertexDescription olddescription = m_description; - m_description = vdd.getDescription(); - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (!olddescription.hasAttribute(i) - && m_description.hasAttribute(i)) { - _afterAddAttributeImpl(i); - } - } + + _assignVertexDescriptionImpl(newdescription); } /** @@ -225,16 +197,9 @@ public void addAttribute(int semantics) { _touch(); if (m_description.hasAttribute(semantics)) return; - - // Create a designer instance out of existing description. - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - m_description); - // Add the attribute to the description designer. - vdd.addAttribute(semantics); - // Obtain new description. - m_description = vdd.getDescription(); - // Let it be known we have added the attribute. - _afterAddAttributeImpl(semantics); + + VertexDescription newvd = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, semantics); + _assignVertexDescriptionImpl(newvd); } /** @@ -248,15 +213,8 @@ public void dropAttribute(int semantics) { if (!m_description.hasAttribute(semantics)) return; - // Create a designer instance out of existing description. - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - m_description); - // Remove the attribute from the description designer. - vdd.removeAttribute(semantics); - // Let know we are dropping the attribute. - _beforeDropAttributeImpl(semantics); - // Obtain new description. - m_description = vdd.getDescription(); + VertexDescription newvd = VertexDescriptionDesignerImpl.removeSemanticsFromVertexDescription(m_description, semantics); + _assignVertexDescriptionImpl(newvd); } /** @@ -383,18 +341,6 @@ public double calculateLength2D() { return 0; } - /* - * Called before the new description pointer is set. Caller is supposed to - * get rid of the Geometry attribute value. - */ - abstract void _beforeDropAttributeImpl(int semantics); - - /* - * Called after the new description pointer is set. Caller is supposed to - * add the Geometry attribute value. - */ - abstract void _afterAddAttributeImpl(int semantics); - protected Object _getImpl() { throw new RuntimeException("invalid call"); } diff --git a/src/com/esri/core/geometry/JsonCursor.java b/src/com/esri/core/geometry/JsonCursor.java index eba7987d..30e1beed 100644 --- a/src/com/esri/core/geometry/JsonCursor.java +++ b/src/com/esri/core/geometry/JsonCursor.java @@ -26,7 +26,7 @@ /** * An abstract Json String Cursor class. */ -abstract class JsonCursor { +public abstract class JsonCursor { /** * Moves the cursor to the next string. Returns null when reached the end. diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java index cbbf9b9d..b8102c0c 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -36,17 +36,10 @@ abstract class MultiVertexGeometry extends Geometry implements Serializable { @Override - void _afterAddAttributeImpl(int semantics) { - // TODO Auto-generated method stub - - } - - @Override - void _beforeDropAttributeImpl(int semantics) { - // TODO Auto-generated method stub - + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + throw new GeometryException("invalid call"); } - + // Multipart methods: /** * Returns the total vertex count in this Geometry. diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java index 80c69315..60836fa2 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -424,59 +424,35 @@ public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { notifyModified(DirtyFlags.DirtyAll); } - // Checked vs. Jan 11, 2011 @Override - void _beforeDropAttributeImpl(int semantics) { - _touch(); - int attributeIndex = m_description.getAttributeIndex(semantics); - // _ASSERT(attributeIndex >= 0); - - AttributeStreamBase[] newAttributes = null; - if (m_vertexAttributes != null) { - newAttributes = new AttributeStreamBase[m_description - .getAttributeCount() - 1]; - - for (int i = 0; i < attributeIndex; i++) - newAttributes[i] = m_vertexAttributes[i]; - for (int i = attributeIndex + 1; i < m_description - .getAttributeCount(); i++) - newAttributes[i - 1] = m_vertexAttributes[i]; - } - - m_vertexAttributes = newAttributes; // late assignment to try to stay - // valid if out-of memory happens. - notifyModified(DirtyFlags.DirtyAll); - } - - // Checked vs. Jan 11, 2011 - @Override - void _afterAddAttributeImpl(int semantics) { - _touch(); - int attributeIndex = m_description.getAttributeIndex(semantics); - // _ASSERT(attributeIndex >= 0); - + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { AttributeStreamBase[] newAttributes = null; + if (m_vertexAttributes != null) { - newAttributes = new AttributeStreamBase[m_description - .getAttributeCount()]; + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes( + newDescription, m_description); + + newAttributes = new AttributeStreamBase[newDescription + .getAttributeCount()]; + + for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { + if (mapping[i] != -1) { + int m = mapping[i]; + newAttributes[i] = m_vertexAttributes[m]; + } - if (m_vertexAttributes != null) { - // Do not create new stream The stream will be created when one - // queries or sets a point to it. - for (int i = 0; i < attributeIndex; i++) - newAttributes[i] = m_vertexAttributes[i]; - for (int i = attributeIndex + 1; i < m_description - .getAttributeCount(); i++) - newAttributes[i] = m_vertexAttributes[i - 1]; } } - + else { + //if there are no streams we do not create them + } + + m_description = newDescription; m_vertexAttributes = newAttributes; // late assignment to try to stay - // valid if out-of memory happens. m_reservedPointCount = -1;// we need to recreate the new attribute then notifyModified(DirtyFlags.DirtyAll); } - + // Checked vs. Jan 11, 2011 protected void _updateEnvelope(Envelope2D env) { _updateAllDirtyIntervals(true); diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/com/esri/core/geometry/OperatorExportToGeoJson.java index 2eeba10f..9a1ac765 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -33,7 +33,7 @@ public Type getType() { return Type.ExportToGeoJson; } - abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); + public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); public abstract String execute(SpatialReference spatialReference, Geometry geometry); diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 3e0ccb17..0abbadb5 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -24,7 +24,7 @@ class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { @Override - JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { + public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { return new OperatorExportToGeoJsonCursor(false, spatialReference, geometryCursor); } diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/com/esri/core/geometry/OperatorExportToJson.java index 60729155..9b548d4f 100644 --- a/src/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/com/esri/core/geometry/OperatorExportToJson.java @@ -42,7 +42,7 @@ public Type getType() { * * @return Returns a JsonCursor. */ - abstract JsonCursor execute(SpatialReference spatialReference, + public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); /** diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java index 8b93cb1b..5c5f819d 100644 --- a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -28,7 +28,7 @@ class OperatorExportToJsonLocal extends OperatorExportToJson { @Override - JsonCursor execute(SpatialReference spatialReference, + public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { return new OperatorExportToJsonCursor(spatialReference, geometryCursor); } diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/com/esri/core/geometry/OperatorIntersectionCursor.java index 3d07cc5a..e23c3e8a 100644 --- a/src/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -483,18 +483,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { polygonQuadTree = accel.getQuadTree(); if (polygonQuadTree == null && polygonImpl.getPointCount() > 20) { - Envelope2D env = new Envelope2D(); - polygon.queryEnvelope2D(env); - QuadTreeImpl polygonQuadTreeNew = new QuadTreeImpl(env, 8); - while (polygonIter.nextPath()) { - while (polygonIter.hasNextSegment()) { - Segment seg = polygonIter.nextSegment(); - seg.queryEnvelope2D(env); - polygonQuadTreeNew.insert(polygonIter.getStartPointIndex(), - env); - } - } - polygonQuadTree = polygonQuadTreeNew; + polygonQuadTree = InternalUtils.buildQuadTree(polygonImpl); } Polyline result_polyline = (Polyline) polyline.createInstance(); diff --git a/src/com/esri/core/geometry/Point.java b/src/com/esri/core/geometry/Point.java index ea4c02c7..30db22e7 100644 --- a/src/com/esri/core/geometry/Point.java +++ b/src/com/esri/core/geometry/Point.java @@ -364,41 +364,44 @@ public void setEmpty() { } @Override - void _afterAddAttributeImpl(int semantics) { - _touch(); - if (m_attributes == null) - return; - - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex); - int comps = VertexDescription.getComponentCount(semantics); - int totalComps = m_description._getTotalComponents(); - resizeAttributes(totalComps); - - for (int i = totalComps - 1; i >= offset + comps; i--) - m_attributes[i] = m_attributes[i - comps]; - - double dv = VertexDescription.getDefaultValue(semantics); - for (int i = 0; i < comps; i++) - m_attributes[offset + i] = dv; - } - - @Override - void _beforeDropAttributeImpl(int semantics) { - _touch(); - if (m_attributes == null) + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; return; - - // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, - // POSITION)); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex); - int comps = VertexDescription.getComponentCount(semantics); - int totalCompsOld = m_description._getTotalComponents(); - if (totalCompsOld > comps) { - for (int i = offset + comps; i < totalCompsOld; i++) - m_attributes[i - comps] = m_attributes[i]; } + + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[newDescription._getTotalComponents()]; + + 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++) + { + 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++; + } + } + + } + + m_attributes = newAttributes; + m_description = newDescription; } /** @@ -531,8 +534,8 @@ private void resizeAttributes(int newSize) { } static void attributeCopy(double[] src, double[] dst, int count) { - for (int i = 0; i < count; i++) - dst[i] = src[i]; + if (count > 0) + System.arraycopy(src, 0, dst, 0, count); } /** diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java index 35979c74..661cb244 100644 --- a/src/com/esri/core/geometry/Point2D.java +++ b/src/com/esri/core/geometry/Point2D.java @@ -33,10 +33,10 @@ * Basic 2D point class. Contains only two double fields. * */ -final class Point2D { +public final class Point2D { public double x; - public double y; + double y; public Point2D() { } @@ -46,7 +46,6 @@ public Point2D(double x, double y) { this.y = y; } - // Header definitions public static Point2D construct(double x, double y) { return new Point2D(x, y); } @@ -65,7 +64,7 @@ public boolean isEqual(Point2D other) { return x == other.x && y == other.y; } - boolean isEqual(Point2D other, double tol) { + public boolean isEqual(Point2D other, double tol) { return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); } @@ -74,7 +73,7 @@ public void sub(Point2D other) { y -= other.y; } - void sub(Point2D p1, Point2D p2) { + public void sub(Point2D p1, Point2D p2) { x = p1.x - p2.x; y = p1.y - p2.y; } @@ -132,12 +131,12 @@ public void scale(double f) { /** * Compares two vertices lexicographicaly. */ - int compare(Point2D other) { + public int compare(Point2D other) { return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 : (x > other.x ? 1 : 0))); } - void normalize(Point2D other) { + public void normalize(Point2D other) { double len = other.length(); if (len == 0) { x = 1.0; @@ -163,15 +162,15 @@ public double length() { return Math.sqrt(x * x + y * y); } - double sqrLength() { + public double sqrLength() { return x * x + y * y; } - static double distance(Point2D pt1, Point2D pt2) { + public static double distance(Point2D pt1, Point2D pt2) { return Math.sqrt(sqrDistance(pt1, pt2)); } - double dotProduct(Point2D other) { + public double dotProduct(Point2D other) { return x * other.x + y * other.y; } @@ -179,11 +178,11 @@ static double distance(Point2D pt1, Point2D pt2) { return Math.abs(x * other.x) + Math.abs(y * other.y); } - double crossProduct(Point2D other) { + public double crossProduct(Point2D other) { return x * other.y - y * other.x; } - void rotateDirect(double Cos, double Sin) // corresponds to the + public void rotateDirect(double Cos, double Sin) // corresponds to the // Transformation2D.SetRotate(cos, // sin).Transform(pt) { @@ -193,7 +192,7 @@ void rotateDirect(double Cos, double Sin) // corresponds to the y = yy; } - void rotateReverse(double Cos, double Sin) { + public void rotateReverse(double Cos, double Sin) { double xx = x * Cos + y * Sin; double yy = -x * Sin + y * Cos; x = xx; @@ -204,7 +203,7 @@ void rotateReverse(double Cos, double Sin) { * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), * sin(pi/2)). */ - void leftPerpendicular() { + public void leftPerpendicular() { double xx = x; x = -y; y = xx; @@ -214,7 +213,7 @@ void leftPerpendicular() { * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), * sin(pi/2)). */ - void leftPerpendicular(Point2D pt) { + public void leftPerpendicular(Point2D pt) { x = -pt.y; y = pt.x; } @@ -223,7 +222,7 @@ void leftPerpendicular(Point2D pt) { * 270 degree rotation, anticlockwise. Equivalent to * RotateDirect(-cos(pi/2), sin(-pi/2)). */ - void rightPerpendicular() { + public void rightPerpendicular() { double xx = x; x = y; y = -xx; @@ -233,7 +232,7 @@ void rightPerpendicular() { * 270 degree rotation, anticlockwise. Equivalent to * RotateDirect(-cos(pi/2), sin(-pi/2)). */ - void rightPerpendicular(Point2D pt) { + public void rightPerpendicular(Point2D pt) { x = pt.y; y = -pt.x; } @@ -300,22 +299,12 @@ public int compare(Point2D v1, Point2D v2) { } } - // Header definitions - - // public Point2D mul(double factor) { - // return new Point2D(x * factor, y * factor); - // } - public static double sqrDistance(Point2D pt1, Point2D pt2) { double dx = pt1.x - pt2.x; double dy = pt1.y - pt2.y; return dx * dx + dy * dy; } - // Header definitions - - // Cpp definitions - @Override public String toString() { return "(" + x + " , " + y + ")"; @@ -383,7 +372,7 @@ public boolean isNaN() { * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use * high precision arithmetics for some special degenerate cases. */ - static int orientationRobust(Point2D p, Point2D q, Point2D r) { + public static int orientationRobust(Point2D p, Point2D q, Point2D r) { ECoordinate det_ec = new ECoordinate(); det_ec.set(q.x); det_ec.sub(p.x); diff --git a/src/com/esri/core/geometry/Segment.java b/src/com/esri/core/geometry/Segment.java index 4f9b131d..a36ab086 100644 --- a/src/com/esri/core/geometry/Segment.java +++ b/src/com/esri/core/geometry/Segment.java @@ -439,7 +439,7 @@ private Point3D _getXYZ(int endPoint) { } if (m_description.hasZ()) - pt.z = m_attributes[_getEndPointOffset(endPoint)]; + pt.z = m_attributes[_getEndPointOffset(m_description, endPoint)]; else pt.z = VertexDescription.getDefaultValue(Semantics.Z); @@ -472,67 +472,57 @@ private void _setXYZ(int endPoint, Point3D pt) { } if (bHasZ) - m_attributes[_getEndPointOffset(endPoint)] = pt.z; + m_attributes[_getEndPointOffset(m_description, endPoint)] = pt.z; } @Override - void _beforeDropAttributeImpl(int semantics) { - _touch(); - if (isEmptyImpl()) + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; return; - - // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, - // POSITION)); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalCompsOld = m_description._getTotalComponents() - 2; - if (totalCompsOld > comps) { - int offset0 = _getEndPointOffset(0); - for (int i = offset + comps; i < totalCompsOld * 2; i++) - m_attributes[offset0 + i - comps] = m_attributes[offset0 + i]; - - int offset1 = _getEndPointOffset(1) - comps; // -comp is for deleted - // attribute of - // start vertex - for (int i = offset + comps; i < totalCompsOld; i++) - m_attributes[offset1 + i - comps] = m_attributes[offset1 + i]; } - } - - @Override - void _afterAddAttributeImpl(int semantics) { - _touch(); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalComps = m_description._getTotalComponents() - 2; - _resizeAttributes(totalComps); - int totalCompsOld = totalComps - comps; // the total number of - // components before resize. - - int offset0 = _getEndPointOffset(0); - int offset1 = _getEndPointOffset(1); - int offset1old = offset1 - comps; - for (int i = totalCompsOld - 1; i >= 0; i--) {// correct the position of - // the End attributes - m_attributes[offset1 + i] = m_attributes[offset1old + i]; - } - - // move attributes for start end end points that go after the insertion - // point - for (int i = totalComps - 1; i >= offset + comps; i--) { - m_attributes[offset0 + i] = m_attributes[offset0 + i - comps]; - m_attributes[offset1 + i] = m_attributes[offset1 + i - comps]; - } - - // initialize added attribute to the default value. - double dv = VertexDescription.getDefaultValue(semantics); - for (int i = 0; i < comps; i++) { - m_attributes[offset0 + offset + i] = dv; - m_attributes[offset1 + offset + i] = dv; + + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + + int old_offset0 = _getEndPointOffset(m_description, 0); + int old_offset1 = _getEndPointOffset(m_description, 1); + + int new_offset0 = _getEndPointOffset(newDescription, 0); + int new_offset1 = _getEndPointOffset(newDescription, 1); + + 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) + { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = d; + newAttributes[new_offset1 + j] = d; + j++; + } + } + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; + newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; + j++; + offset++; + } + } + } + + m_attributes = newAttributes; + m_description = newDescription; } private void _get(int endPoint, Point outPoint) { @@ -595,7 +585,7 @@ private void _set(int endPoint, Point src) { if (m_attributes != null) _resizeAttributes(m_description._getTotalComponents() - 2); - return m_attributes[_getEndPointOffset(endPoint) + return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; } else @@ -622,11 +612,12 @@ void _setAttribute(int endPoint, int semantics, int ordinate, double value) { } if (semantics == Semantics.POSITION) { - if (endPoint != 0) + if (endPoint != 0) { if (ordinate != 0) m_yEnd = value; else m_xEnd = value; + } else if (ordinate != 0) m_yStart = value; else @@ -634,10 +625,10 @@ else if (ordinate != 0) return; } - if (m_attributes != null) + if (m_attributes == null) _resizeAttributes(m_description._getTotalComponents() - 2); - m_attributes[_getEndPointOffset(endPoint) + m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; @@ -786,8 +777,8 @@ int _intersect(Segment other, Point2D[] intersectionPoints, */ abstract double _calculateArea2DHelper(double xorg, double yorg); - int _getEndPointOffset(int endPoint) { - return endPoint * (m_description._getTotalComponents() - 2); + static int _getEndPointOffset(VertexDescription vd, int endPoint) { + return endPoint * (vd._getTotalComponents() - 2); } /** diff --git a/src/com/esri/core/geometry/SpatialReference.java b/src/com/esri/core/geometry/SpatialReference.java index c2621096..a416c241 100644 --- a/src/com/esri/core/geometry/SpatialReference.java +++ b/src/com/esri/core/geometry/SpatialReference.java @@ -29,6 +29,7 @@ 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; @@ -87,48 +88,87 @@ boolean isLocal() { * if parsing has failed */ public static SpatialReference fromJson(JsonParser parser) throws Exception { - int wkid = 0; + return fromJson(new JsonParserReader(parser)); + } + + 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; + boolean bFoundLatestWkid = false; + boolean bFoundVcsWkid = false; + boolean bFoundLatestVcsWkid = false; + boolean bFoundWkt = false; + + int wkid = -1; + int latestWkid = -1; + int vcs_wkid = -1; + int latestVcsWkid = -1; String wkt = null; - if (!JSONUtils.isObjectStart(parser)) - return null; - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); + String name = parser.currentString(); parser.nextToken(); - if (parser.getCurrentToken() == JsonToken.VALUE_NULL) { - continue; + + if (!bFoundWkid && name.equals("wkid")) { + bFoundWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + wkid = parser.currentIntValue(); + } else if (!bFoundLatestWkid && name.equals("latestWkid")) { + bFoundLatestWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + latestWkid = parser.currentIntValue(); + } else if (!bFoundWkt && name.equals("wkt")) { + bFoundWkt = true; + + if (parser.currentToken() == JsonToken.VALUE_STRING) + wkt = parser.currentString(); + } else if (!bFoundVcsWkid && name.equals("vcsWkid")) { + bFoundVcsWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + vcs_wkid = parser.currentIntValue(); + } else if (!bFoundLatestVcsWkid && name.equals("latestVcsWkid")) { + bFoundLatestVcsWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + latestVcsWkid = parser.currentIntValue(); } + } + + if (latestVcsWkid <= 0 && vcs_wkid > 0) { + latestVcsWkid = vcs_wkid; + } + + // iter.step_out_after(); do not step out for the spatial reference, + // because this method is used standalone + + SpatialReference spatial_reference = null; - if ("latestWkid".equals(fieldName)) {// get wkid - wkid = parser.getIntValue(); - } else if ("wkid".equals(fieldName)) {// get wkid - wkid = parser.getIntValue(); - } else if ("wkt".equals(fieldName)) { - wkt = parser.getText(); - } else { - parser.skipChildren(); + if (wkt != null && wkt.length() != 0) { + try { + spatial_reference = SpatialReference.create(wkt); + } catch (Exception e) { } } - // END _OBJECT - if (wkid > 0) // 1. Try to use wkid - { + if (spatial_reference == null && latestWkid > 0) { try { - return SpatialReference.create(wkid); - } catch (IllegalArgumentException ex) { - // if (wkt == null || wkt.length() == 0) //Here this will be our - // default. - // throw ex; + spatial_reference = SpatialReference.create(latestWkid); + } catch (Exception e) { } } - if (wkt != null && wkt.length() != 0) // try to use wkt. - { - return SpatialReference.create(wkt); + if (spatial_reference == null && wkid > 0) { + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } } - return null; - } + return spatial_reference; + } /** * Returns the well known ID for the horizontal coordinate system of the diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 3ead4792..9bec3a33 100644 --- a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -24,6 +24,10 @@ package com.esri.core.geometry; +import java.util.Arrays; + +import com.esri.core.geometry.VertexDescription.Semantics; + /** * This factory class allows to describe and create a VertexDescription * instance. @@ -207,5 +211,52 @@ public boolean isDesignerFor(VertexDescription vd) { 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); + for (int i = 0, nsrc = src.getAttributeCount(); i < nsrc; i++) { + srcToDst[i] = dest.getAttributeIndex(src.getSemantics(i)); + } + 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/unittest/com/esri/core/geometry/TestAttributes.java b/unittest/com/esri/core/geometry/TestAttributes.java new file mode 100644 index 00000000..fe3f6a67 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestAttributes.java @@ -0,0 +1,324 @@ +package com.esri.core.geometry; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class TestAttributes { + + @Test + public void testPoint() { + Point pt = new Point(); + pt.setXY(100, 200); + assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); + pt.addAttribute(VertexDescription.Semantics.M); + assertTrue(pt.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(pt.getM())); + pt.setAttribute(VertexDescription.Semantics.M, 0, 13); + assertTrue(pt.getM() == 13); + pt.addAttribute(VertexDescription.Semantics.Z); + assertTrue(pt.getZ() == 0); + assertTrue(pt.getM() == 13); + pt.setAttribute(VertexDescription.Semantics.Z, 0, 11); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.addAttribute(VertexDescription.Semantics.ID); + assertTrue(pt.getID() == 0); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.setAttribute(VertexDescription.Semantics.ID, 0, 1); + assertTrue(pt.getID() == 1); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.dropAttribute(VertexDescription.Semantics.M); + assertTrue(pt.getID() == 1); + assertTrue(pt.getZ() == 11); + assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); + + Point pt1 = new Point(); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.Z)); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.ID)); + pt1.mergeVertexDescription(pt.getDescription()); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(pt1.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(pt1.hasAttribute(VertexDescription.Semantics.ID)); + } + + @Test + public void testEnvelope() { + Envelope env = new Envelope(); + env.setCoords(100, 200, 250, 300); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + env.addAttribute(VertexDescription.Semantics.M); + assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).isEmpty()); + env.setInterval(VertexDescription.Semantics.M, 0, 1, 2); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); + + assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); + env.addAttribute(VertexDescription.Semantics.Z); + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 0); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 0); + env.setInterval(VertexDescription.Semantics.Z, 0, 3, 4); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + + + assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); + env.addAttribute(VertexDescription.Semantics.ID); + assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 0); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 0); + env.setInterval(VertexDescription.Semantics.ID, 0, 5, 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); + + env.dropAttribute(VertexDescription.Semantics.M); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + + Envelope env1 = new Envelope(); + env.copyTo(env1); + assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + } + + @Test + public void testLine() { + Line env = new Line(); + env.setStartXY(100, 200); + env.setEndXY(250, 300); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + env.addAttribute(VertexDescription.Semantics.M); + assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); + env.setStartAttribute(VertexDescription.Semantics.M, 0, 1); + env.setEndAttribute(VertexDescription.Semantics.M, 0, 2); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); + + assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); + env.addAttribute(VertexDescription.Semantics.Z); + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + env.setStartAttribute(VertexDescription.Semantics.Z, 0, 3); + env.setEndAttribute(VertexDescription.Semantics.Z, 0, 4); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + + assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); + env.addAttribute(VertexDescription.Semantics.ID); + assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); + env.setStartAttribute(VertexDescription.Semantics.ID, 0, 5); + env.setEndAttribute(VertexDescription.Semantics.ID, 0, 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + env.dropAttribute(VertexDescription.Semantics.M); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + Line env1 = new Line(); + env.copyTo(env1); + assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + } + + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(new Point(100, 200)); + mp.add(new Point(101, 201)); + mp.add(new Point(102, 202)); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + mp.addAttribute(VertexDescription.Semantics.M); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); + mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); + mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); + mp.addAttribute(VertexDescription.Semantics.Z); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); + mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); + mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); + mp.addAttribute(VertexDescription.Semantics.ID); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); + mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); + mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp.dropAttribute(VertexDescription.Semantics.M); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + MultiPoint mp1 = new MultiPoint(); + mp.copyTo(mp1); + assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp1.dropAllAttributes(); + mp1.mergeVertexDescription(mp.getDescription()); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + + } + + @Test + public void testPolygon() { + Polygon mp = new Polygon(); + mp.startPath(new Point(100, 200)); + mp.lineTo(new Point(101, 201)); + mp.lineTo(new Point(102, 202)); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + mp.addAttribute(VertexDescription.Semantics.M); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); + mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); + mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); + mp.addAttribute(VertexDescription.Semantics.Z); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); + mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); + mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); + mp.addAttribute(VertexDescription.Semantics.ID); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); + mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); + mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp.dropAttribute(VertexDescription.Semantics.M); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + Polygon mp1 = new Polygon(); + mp.copyTo(mp1); + assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp1.dropAllAttributes(); + mp1.mergeVertexDescription(mp.getDescription()); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + + } + +} From 7729b1a738276cb401b477692688a1cb725ad92e Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 20 Oct 2013 14:13:24 -0700 Subject: [PATCH 006/145] Point2D.y has to be public Made Point2D.y public. Some cleanup in the unit tests. --- src/com/esri/core/geometry/Point2D.java | 2 +- .../com/esri/core/geometry/GeometryUtils.java | 131 +----------------- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 2 +- .../com/esri/core/geometry/TestTouch.java | 109 --------------- 4 files changed, 6 insertions(+), 238 deletions(-) diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java index 661cb244..fe1a13b9 100644 --- a/src/com/esri/core/geometry/Point2D.java +++ b/src/com/esri/core/geometry/Point2D.java @@ -36,7 +36,7 @@ public final class Point2D { public double x; - double y; + public double y; public Point2D() { } diff --git a/unittest/com/esri/core/geometry/GeometryUtils.java b/unittest/com/esri/core/geometry/GeometryUtils.java index cddf9957..de3bcf04 100644 --- a/unittest/com/esri/core/geometry/GeometryUtils.java +++ b/unittest/com/esri/core/geometry/GeometryUtils.java @@ -2,103 +2,13 @@ import java.io.File; import java.io.FileNotFoundException; -import java.net.URI; import java.util.Scanner; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.UriBuilder; - import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.config.ClientConfig; -import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.api.representation.Form; - public class GeometryUtils { - static WebResource service4Proj; - static String url4Proj = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/project"; - static WebResource service4Simplify; - static String url4Simplify = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/simplify"; - static WebResource service4Relation; - static String url4Relation = "http://sampleserver1.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer/relation"; - - static { - ClientConfig config = new DefaultClientConfig(); - Client client = Client.create(config); - service4Proj = client.resource(getBaseURI4Proj()); - service4Simplify = client.resource(getBaseURI4Simplify()); - service4Relation = client.resource(url4Relation); - } - - private static URI getBaseURI4Proj() { - return UriBuilder.fromUri(url4Proj).build(); - } - - private static URI getBaseURI4Simplify() { - return UriBuilder.fromUri(url4Simplify).build(); - } - - public static Geometry getGeometryForProjectFromRestWS(int srIn, int srOut, - Geometry geomIn) { - String jsonStr4Geom = GeometryEngine.geometryToJson(srIn, geomIn); - String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) - + "\",\"geometries\":[" + jsonStr4Geom + "]}"; - - Form f = new Form(); - f.add("inSR", srIn); - f.add("outSR", srOut); - f.add("geometries", jsonStrNew); - f.add("f", "json"); - - ClientResponse response = service4Proj.type( - MediaType.APPLICATION_FORM_URLENCODED).post( - ClientResponse.class, f); - @SuppressWarnings("unused") - boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; - Object obj = response.getEntity(String.class); - String jsonStr = obj.toString(); - int idx2 = jsonStr.lastIndexOf("]"); - int idx1 = jsonStr.indexOf("["); - if (idx1 == -1 || idx2 == -1) - return null; - String jsonStrGeom = jsonStr.substring(idx1 + 1, idx2); - Geometry geometryObj = getGeometryFromJSon(jsonStrGeom); - return geometryObj; - } - - public static Geometry getGeometryForSimplifyFromRestWS(int sr, - Geometry geomIn) { - String jsonStr4Geom = GeometryEngine.geometryToJson( - SpatialReference.create(sr), geomIn); - String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) - + "\",\"geometries\":[" + jsonStr4Geom + "]}"; - - Form f = new Form(); - f.add("sr", sr); - f.add("geometries", jsonStrNew); - f.add("f", "json"); - - ClientResponse response = service4Simplify.type( - MediaType.APPLICATION_FORM_URLENCODED).post( - ClientResponse.class, f); - @SuppressWarnings("unused") - boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; - Object obj = response.getEntity(String.class); - String jsonStr = obj.toString(); - int idx2 = jsonStr.lastIndexOf("]"); - int idx1 = jsonStr.indexOf("["); - if (idx1 == -1 || idx2 == -1) - return null; - String jsonStrGeom = jsonStr.substring(idx1 + 1, idx2); - Geometry geometryObj = getGeometryFromJSon(jsonStrGeom); - return geometryObj; - } - - static String getGeometryType(Geometry geomIn) { + public static String getGeometryType(Geometry geomIn) { // there are five types: esriGeometryPoint // esriGeometryMultipoint // esriGeometryPolyline @@ -132,7 +42,8 @@ static Geometry getGeometryFromJSon(String jsonStr) { } public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { - int count1 = mp1.getPointCount(); + return; + /*int count1 = mp1.getPointCount(); int count2 = mp2.getPointCount(); System.out.println("From Rest vertices count: " + count1); @@ -155,47 +66,13 @@ public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { // Assert.assertTrue(deltaX<1e-7); // Assert.assertTrue(deltaY<1e-7); } + */ } public enum SpatialRelationType { esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation } - public static boolean isRelationTrue(Geometry geometry1, - Geometry geometry2, SpatialReference sr, - SpatialRelationType relation, String relationParam) { - String jsonStr4Geom1 = getJSonStringFromGeometry(geometry1, sr); - String jsonStr4Geom2 = getJSonStringFromGeometry(geometry2, sr); - - Form f = new Form(); - f.add("sr", sr.getID()); - f.add("geometries1", jsonStr4Geom1); - f.add("geometries2", jsonStr4Geom2); - - @SuppressWarnings("unused") - String enumName = relation.name(); - - f.add("relation", relation.name()); - f.add("f", "json"); - f.add("relationParam", relationParam); - - ClientResponse response = service4Relation.type( - MediaType.APPLICATION_FORM_URLENCODED).post( - ClientResponse.class, f); - @SuppressWarnings("unused") - boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; - Object obj = response.getEntity(String.class); - String jsonStr = obj.toString(); - int idx = jsonStr - .lastIndexOf("geometry1Index\":0,\"geometry2Index\":0"); - - if (idx == -1) { - return false; - } else { - return true; - } - } - static String getJSonStringFromGeometry(Geometry geomIn, SpatialReference sr) { String jsonStr4Geom = GeometryEngine.geometryToJson(sr, geomIn); String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 8d32bbe2..9a12ac11 100644 --- a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -552,7 +552,7 @@ boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, return false; if (!(Wkt != null && Wkt.length() > 0)) return false; - System.out.println("WKT1: " + Wkt); + //System.out.println("WKT1: " + Wkt); SpatialReference sr2 = SpatialReference.create(Wkt); int wki2 = sr2.getID(); if (expectWki2 > 0) { diff --git a/unittest/com/esri/core/geometry/TestTouch.java b/unittest/com/esri/core/geometry/TestTouch.java index c12d3999..b677ba7d 100644 --- a/unittest/com/esri/core/geometry/TestTouch.java +++ b/unittest/com/esri/core/geometry/TestTouch.java @@ -35,15 +35,6 @@ public void testTouchOnPointAndPolyline() { isTouched2 = false; } assertEquals(isTouched && isTouched2, true); - - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - pl, - baseGeom, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -67,15 +58,6 @@ public void testTouchOnPointAndPolygon() { isTouched2 = false; } assertEquals(isTouched && isTouched2, true); - - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - pg, - baseGeom, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -139,14 +121,6 @@ public void testTouchesOnPolylines() { isTouched = false; } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -175,14 +149,6 @@ public void testTouchesOnPolylineAndPolygon() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -205,14 +171,6 @@ public void testTouchOnEnvelopes() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - env, - env2, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -238,14 +196,6 @@ public void testTouchesOnPolylineAndEnvelope() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - env, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -270,14 +220,6 @@ public void testTouchesOnPolygonAndEnvelope() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - env, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -297,14 +239,6 @@ public void testTouchesOnPointAndEnvelope() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - p, - env, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -325,14 +259,6 @@ public void testRelationTouch() { // "G1 TOUCH G2"); assertEquals(isTouched, false); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -356,17 +282,6 @@ public void testTouchesBetweenPointAndLine() { boolean isTouched = GeometryEngine.touches(p, compPl, sr); assertTrue(!isTouched); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - p, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); - // We do not treat polyline that is not explicitly closed as closed. - // Keep the case to demonstrate the difference between ArcObjects and - // Borg here. } @Test @@ -394,14 +309,6 @@ public void testTouchesBetweenPolylines() { boolean isTouched = GeometryEngine.touches(pl, compPl, sr); assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - pl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -433,14 +340,6 @@ public void testTouchesBetweenPolylineAndPolygon() { boolean isTouched = GeometryEngine.touches(pl, compPg, sr); assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPg, - pl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -507,14 +406,6 @@ public void testTouchesBetweenMultipartPolygons2() { boolean isTouched = GeometryEngine.touches(pl, compPl, sr); assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - pl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test From 0b4d55d8e23ed7b7b848158fb04c33f1fd3fa27c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 8 Jan 2014 17:50:07 -0800 Subject: [PATCH 007/145] Envelope2D serialize; OperatorDistance optimized for Point to Envelope;A bug in Polyline vs Polygon intersection;SimpleRasterizer for acceleration --- src/com/esri/core/geometry/Envelope2D.java | 43 +- src/com/esri/core/geometry/JsonReader.java | 2 +- .../core/geometry/OperatorDistanceLocal.java | 33 +- .../geometry/OperatorIntersectionCursor.java | 1 + .../geometry/RasterizedGeometry2DImpl.java | 552 +++++++++++++++++- .../esri/core/geometry/SimpleRasterizer.java | 377 ++++++++++++ src/com/esri/core/geometry/Simplificator.java | 3 +- .../geometry/StridedIndexTypeCollection.java | 23 +- .../com/esri/core/geometry/TestDistance.java | 8 + .../esri/core/geometry/TestIntersection.java | 12 +- unittest/com/esri/core/geometry/TestOGC.java | 22 +- .../geometry/TestRasterizedGeometry2D.java | 134 +++++ .../esri/core/geometry/TestSerialization.java | 46 +- .../esri/core/geometry/savedEnvelope2D.txt | Bin 0 -> 115 bytes 14 files changed, 1228 insertions(+), 28 deletions(-) create mode 100644 src/com/esri/core/geometry/SimpleRasterizer.java create mode 100644 unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java create mode 100644 unittest/com/esri/core/geometry/savedEnvelope2D.txt diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index a3f09cf5..30aa184b 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -24,17 +24,21 @@ package com.esri.core.geometry; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; + /** * An axis parallel 2-dimensional rectangle. */ -public final class Envelope2D { - - private final int XLESSXMIN = 1; +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 int YLESSYMIN = 4; + private final static int YLESSYMIN = 4; // private final int YGREATERYMAX = 8; - private final int XMASK = 3; - private final int YMASK = 12; + private final static int XMASK = 3; + private final static int YMASK = 12; public double xmin; @@ -994,16 +998,28 @@ public boolean isPointOnBoundary(Point2D pt, double tolerance) { || Math.abs(pt.y - ymax) <= tolerance; } + /** + * Calculates minimum distance from this envelope to the other. + * Returns 0 for empty envelopes. + */ public double distance(/* const */Envelope2D other) { return Math.sqrt(sqrDistance(other)); } + /** + * Calculates minimum distance from this envelope to the point. + * Returns 0 for empty envelopes. + */ public double distance(Point2D pt2D) { return Math.sqrt(sqrDistance(pt2D)); } + /** + * Calculates minimum squared distance from this envelope to the other. + * Returns 0 for empty envelopes. + */ public double sqrDistance(Envelope2D other) { double dx = 0; @@ -1029,6 +1045,10 @@ public double sqrDistance(Envelope2D other) return dx * dx + dy * dy; } + /** + * Calculates minimum squared distance from this envelope to the point. + * Returns 0 for empty envelopes. + */ public double sqrDistance(Point2D pt2D) { double dx = 0; @@ -1071,4 +1091,15 @@ public void queryIntervalY(Envelope1D env1D) env1D.setCoords(ymin, ymax); } } + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + } + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } + private void readObjectNoData() throws ObjectStreamException { + setEmpty(); + } + } diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java index 065ecafe..10e4d0c7 100644 --- a/src/com/esri/core/geometry/JsonReader.java +++ b/src/com/esri/core/geometry/JsonReader.java @@ -315,7 +315,7 @@ Object getCurrentObject() { throw new GeometryException("invalid call"); } - return m_jsonObject.get(m_keys[m_currentIndex]); + return m_jsonObject.opt(m_keys[m_currentIndex]); } boolean next() { diff --git a/src/com/esri/core/geometry/OperatorDistanceLocal.java b/src/com/esri/core/geometry/OperatorDistanceLocal.java index 12bc7c48..76ac6a35 100644 --- a/src/com/esri/core/geometry/OperatorDistanceLocal.java +++ b/src/com/esri/core/geometry/OperatorDistanceLocal.java @@ -41,6 +41,9 @@ public double execute(Geometry geom1, Geometry geom2, Geometry geometryA = geom1; Geometry geometryB = geom2; + if (geometryA.isEmpty() || geometryB.isEmpty()) + return NumberUtils.TheNaN; + Polygon polygonA; Polygon polygonB; MultiPoint multiPointA; @@ -49,23 +52,41 @@ public double execute(Geometry geom1, Geometry geom2, // if geometryA is an envelope use a polygon instead (if geom1 was // folded, then geometryA will already be a polygon) // if geometryA is a point use a multipoint instead - if (geometryA.getType().equals(Geometry.Type.Point)) { + Geometry.Type gtA = geometryA.getType(); + Geometry.Type gtB = geometryB.getType(); + if (gtA == Geometry.Type.Point) { + if (gtB == Geometry.Type.Point) { + return Point2D.distance(((Point)geometryA).getXY(), ((Point)geometryB).getXY()); + } + else if (gtB == Geometry.Type.Envelope) { + Envelope2D envB = new Envelope2D(); + geometryB.queryEnvelope2D(envB); + return envB.distance(((Point)geometryA).getXY()); + } + multiPointA = new MultiPoint(); multiPointA.add((Point) geometryA); geometryA = multiPointA; - } else if (geometryA.getType().equals(Geometry.Type.Envelope)) { + } else if (gtA == Geometry.Type.Envelope) { + if (gtB == Geometry.Type.Envelope) { + Envelope2D envA = new Envelope2D(); + geometryA.queryEnvelope2D(envA); + Envelope2D envB = new Envelope2D(); + geometryB.queryEnvelope2D(envB); + return envB.distance(envA); + } polygonA = new Polygon(); polygonA.addEnvelope((Envelope) geometryA, false); - geometryB = polygonA; + geometryA = polygonA; } // if geom_2 is an envelope use a polygon instead // if geom_2 is a point use a multipoint instead - if (geometryB.getType().equals(Geometry.Type.Point)) { + if (gtB == Geometry.Type.Point) { multiPointB = new MultiPoint(); multiPointB.add((Point) geometryB); geometryB = multiPointB; - } else if (geometryB.getType().equals(Geometry.Type.Envelope)) { + } else if (gtB == Geometry.Type.Envelope) { polygonB = new Polygon(); polygonB.addEnvelope((Envelope) geometryB, false); geometryB = polygonB; @@ -404,7 +425,7 @@ private boolean weakIntersectionTest_(/* const */Geometry geometryA, /* const */ double calculate(/* const */Geometry geometryA, /* const */ Geometry geometryB) { if (geometryA.isEmpty() || geometryB.isEmpty()) - return 0.0; + return NumberUtils.TheNaN; geometryA.queryEnvelope2D(m_env2DgeometryA); geometryB.queryEnvelope2D(m_env2DgeometryB); diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/com/esri/core/geometry/OperatorIntersectionCursor.java index e23c3e8a..9d5437dc 100644 --- a/src/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -607,6 +607,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { resultPolylineImpl.addSegment(resSeg, false); state = stateAddSegment; + inCount = 0; } else { status = analyseClipSegment_(polygon, resSeg, tolerance); diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java index c5252e20..cd9f445f 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -24,14 +24,560 @@ package com.esri.core.geometry; -abstract class RasterizedGeometry2DImpl extends RasterizedGeometry2D { +import java.io.*; +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; + +final class RasterizedGeometry2DImpl extends RasterizedGeometry2D { + int[] m_bitmap; + int m_scanLineSize; + int m_width; + double m_dx; + double m_dy; + double m_x0; + double m_y0; + double m_toleranceXY; + double m_stroke_half_widthX_pix; + double m_stroke_half_widthY_pix; + double m_stroke_half_width; + + Envelope2D m_geomEnv;// envelope of the raster in world coordinates + Transformation2D m_transform; + int m_dbgTestCount; + SimpleRasterizer m_rasterizer; + ScanCallbackImpl m_callback; + + class ScanCallbackImpl implements SimpleRasterizer.ScanCallback { + int[] m_bitmap; + int m_scanlineWidth; + int m_color; + + public ScanCallbackImpl(int[] bitmap, int scanlineWidth) { + m_scanlineWidth = scanlineWidth; + m_bitmap = bitmap; + } + + public void setColor(SimpleRasterizer rasterizer, int color) { + 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 + } + } + } + + void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPathImpl polygon, boolean isWinding) { + SegmentIteratorImpl segIter = polygon.querySegmentIterator(); + Point2D p1 = new Point2D(); + Point2D p2 = new Point2D(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + if (seg.getType() != Geometry.Type.Line) + throw new GeometryException("internal error"); // TODO: + // densify + // the + // segment + // here + trans.transform(seg.getStartXY(), p1); + trans.transform(seg.getEndXY(), p2); + m_rasterizer.addEdge(p1.x, p1.y, p2.x, p2.y); + } + } + + m_rasterizer.renderEdges(isWinding ? SimpleRasterizer.WINDING : SimpleRasterizer.EVEN_ODD); + } + + void fillPoints(SimpleRasterizer rasterizer, MultiPointImpl geom, double stroke_half_width) { + throw new GeometryException("internal error"); + } + + void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { + for (int i = 1, n = len; i < n; i++) { + rasterizer.addEdge(fan[i-1].x, fan[i-1].y, fan[i].x, fan[i].y); + } + rasterizer.addEdge(fan[len-1].x, fan[len-1].y, fan[0].x, fan[0].y); + m_rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + } + + 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); + } + } + } + } + + void strokeDrawPolyPath(SimpleRasterizer rasterizer, + MultiPathImpl polyPath, double tol) { + + Point2D[] fan = new Point2D[4]; + for (int i = 0; i < fan.length; i++) + fan[i] = new Point2D(); + + SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); + double strokeHalfWidth = m_transform.transform(tol) + 1.5; + double shortSegment = 0.5; + 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(); + Envelope2D segEnv = new Envelope2D(); + Point2D ptOld = new Point2D(); + while (segIter.nextPath()) { + boolean hasFan = false; + boolean first = true; + ptOld.setCoords(0, 0); + while (segIter.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + ptStart.x = seg.getStartX(); + ptStart.y = seg.getStartY();// Point2D ptStart = + // seg.getStartXY(); + ptEnd.x = seg.getEndX(); + ptEnd.y = seg.getEndY();// Point2D ptEnd = seg.getEndXY(); + 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); + hasFan = false; + } + first = true; + continue; + } + + m_transform.transform(ptEnd, ptEnd); + + if (first) { + m_transform.transform(ptStart, ptStart); + ptOld.setCoords(ptStart); + first = false; + } else { + 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; + } + + 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) + fillConvexPolygon(rasterizer, fan, 4); + } + } + + int worldToPixX(double x) { + return (int) Math.round(x * m_dx + m_x0); + } + + int worldToPixY(double y) { + return (int) Math.round(y * m_dy + m_y0); + } + + RasterizedGeometry2DImpl(Geometry geom, double toleranceXY, + int rasterSizeBytes) { + // //_ASSERT(CanUseAccelerator(geom)); + init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, + rasterSizeBytes); + } + static RasterizedGeometry2DImpl createImpl(Geometry geom, double toleranceXY, int rasterSizeBytes) { - return null; + RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, + toleranceXY, rasterSizeBytes); + rgImpl.init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, + rasterSizeBytes); + return rgImpl; + } + + private RasterizedGeometry2DImpl(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + init(geom, toleranceXY, rasterSizeBytes); } static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, double toleranceXY, int rasterSizeBytes) { - return null; + RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, + toleranceXY, rasterSizeBytes); + rgImpl.init(geom, toleranceXY, rasterSizeBytes); + return rgImpl; + } + + void init(MultiVertexGeometryImpl geom, double toleranceXY, + int rasterSizeBytes) { + // _ASSERT(CanUseAccelerator(geom)); + m_width = Math.max((int) (Math.sqrt(rasterSizeBytes) * 2 + 0.5), 64); + m_scanLineSize = (m_width * 2 + 31) / 32; // 2 bits per pixel + m_geomEnv = new Envelope2D(); + + m_toleranceXY = toleranceXY; + + // calculate bitmap size + int size = 0; + int width = m_width; + int scanLineSize = m_scanLineSize; + while (width >= 8) { + size += width * scanLineSize; + width /= 2; + scanLineSize = (width * 2 + 31) / 32; + } + + // allocate the bitmap, that contains the base and the mip-levels + m_bitmap = new int[size]; + for (int i = 0; i < size; i++) + m_bitmap[i] = 0; + + m_rasterizer = new SimpleRasterizer(); + ScanCallbackImpl callback = new ScanCallbackImpl(m_bitmap, + m_scanLineSize); + m_callback = callback; + m_rasterizer.setup(m_width, m_width, callback); + geom.queryEnvelope2D(m_geomEnv); + if (m_geomEnv.getWidth() > m_width * m_geomEnv.getHeight() + || m_geomEnv.getHeight() > m_geomEnv.getWidth() * m_width) { + // the geometry is thin and the rasterizer is not needed. + } + m_geomEnv.inflate(toleranceXY, toleranceXY); + Envelope2D worldEnv = new Envelope2D(); + + Envelope2D pixEnv = Envelope2D + .construct(1, 1, m_width - 2, m_width - 2); + + double minWidth = toleranceXY * pixEnv.getWidth(); // min width is such + // that the size of + // one pixel is + // equal to the + // tolerance + double minHeight = toleranceXY * pixEnv.getHeight(); + + worldEnv.setCoords(m_geomEnv.getCenter(), + Math.max(minWidth, m_geomEnv.getWidth()), + Math.max(minHeight, m_geomEnv.getHeight())); + + m_stroke_half_widthX_pix = worldEnv.getWidth() / pixEnv.getWidth(); + m_stroke_half_widthY_pix = worldEnv.getHeight() / pixEnv.getHeight(); + + // The stroke half width. Later it will be inflated to account for + // pixels size. + m_stroke_half_width = m_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); + break; + case Geometry.GeometryType.Polyline: + callback.setColor(m_rasterizer, 2); + strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), + m_stroke_half_width); + break; + case Geometry.GeometryType.Polygon: { + boolean isWinding = false;// NOTE: change when winding is supported + callback.setColor(m_rasterizer, 1); + fillMultiPath(m_rasterizer, m_transform, (MultiPathImpl) geom, isWinding); + callback.setColor(m_rasterizer, 2); + strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), + m_stroke_half_width); + } + break; + } + + m_dx = m_transform.xx; + m_dy = m_transform.yy; + m_x0 = m_transform.xd; + m_y0 = m_transform.yd; + buildLevels(); + } + + boolean tryRenderAsSmallEnvelope_(Envelope2D env) { + if (!env.isIntersecting(m_geomEnv)) + return true; + + Envelope2D envPix = new Envelope2D(); + envPix.setCoords(env); + m_transform.transform(env); + double strokeHalfWidthPixX = m_stroke_half_widthX_pix; + double strokeHalfWidthPixY = m_stroke_half_widthY_pix; + if (envPix.getWidth() > 2 * strokeHalfWidthPixX + 1 + || envPix.getHeight() > 2 * strokeHalfWidthPixY + 1) + return false; + + // This envelope is too narrow/small, so that it can be just drawn as a + // rectangle using only boundary color. + + envPix.inflate(strokeHalfWidthPixX, strokeHalfWidthPixY); + envPix.xmax += 1.0; + envPix.ymax += 1.0;// take into account that it does not draw right and + // bottom edges. + + m_callback.setColor(m_rasterizer, 2); + fillEnvelope(m_rasterizer, envPix); + return true; + } + + void buildLevels() { + int iStart = 0; + int iStartNext = m_width * m_scanLineSize; + int width = m_width; + int widthNext = m_width / 2; + int scanLineSize = m_scanLineSize; + int scanLineSizeNext = (widthNext * 2 + 31) / 32; + while (width > 8) { + for (int iy = 0; iy < widthNext; iy++) { + int iysrc1 = iy * 2; + int iysrc2 = iy * 2 + 1; + for (int ix = 0; ix < widthNext; ix++) { + int ixsrc1 = ix * 2; + int ixsrc2 = ix * 2 + 1; + int divix1 = ixsrc1 >> 4; + int modix1 = (ixsrc1 & 15) * 2; + int divix2 = ixsrc2 >> 4; + int modix2 = (ixsrc2 & 15) * 2; + int res = (m_bitmap[iStart + scanLineSize * iysrc1 + divix1] >> modix1) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc1 + divix2] >> modix2) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix1] >> modix1) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix2] >> modix2) & 3; + int divixDst = ix >> 4; + int modixDst = (ix & 15) * 2; + m_bitmap[iStartNext + scanLineSizeNext * iy + divixDst] |= res << modixDst; + } + } + + width = widthNext; + scanLineSize = scanLineSizeNext; + iStart = iStartNext; + widthNext = width / 2; + scanLineSizeNext = (widthNext * 2 + 31) / 32; + iStartNext = iStart + scanLineSize * width; + } + } + + @Override + public HitType queryPointInGeometry(double x, double y) { + int ix = worldToPixX(x); + int iy = worldToPixY(y); + if (ix < 0 || ix >= m_width || iy < 0 || iy >= m_width) + return HitType.Outside; + int divix = ix >> 4; + int modix = (ix & 15) * 2; + int res = (m_bitmap[m_scanLineSize * iy + divix] >> modix) & 3; + if (res == 0) + return HitType.Outside; + else if (res == 1) + return HitType.Inside; + else + return HitType.Border; + } + + @Override + public HitType queryEnvelopeInGeometry(Envelope2D env) { + if (!env.intersect(m_geomEnv)) + return com.esri.core.geometry.RasterizedGeometry2D.HitType.Outside; + int ixmin = worldToPixX(env.xmin); + int ixmax = worldToPixX(env.xmax); + int iymin = worldToPixY(env.ymin); + int iymax = worldToPixY(env.ymax); + if (ixmin < 0) + ixmin = 0; + if (iymin < 0) + iymin = 0; + if (ixmax >= m_width) + ixmax = m_width - 1; + if (iymax >= m_width) + iymax = m_width - 1; + + if (ixmin > ixmax || iymin > iymax) + return HitType.Outside; + + int area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); + int iStart = 0; + int scanLineSize = m_scanLineSize; + int width = m_width; + int res = 0; + while (true) { + if (area < 32 || width < 16) { + for (int iy = iymin; iy <= iymax; iy++) { + for (int ix = ixmin; ix <= ixmax; ix++) { + int divix = ix >> 4; + int modix = (ix & 15) * 2; + res = (m_bitmap[iStart + scanLineSize * iy + divix] >> modix) & 3; // read + // two + // bit + // color. + if (res > 1) + return HitType.Border; + } + } + + if (res == 0) + return HitType.Outside; + else if (res == 1) + return HitType.Inside; + } + + iStart += scanLineSize * width; + width /= 2; + scanLineSize = (width * 2 + 31) / 32; + ixmin /= 2; + iymin /= 2; + ixmax /= 2; + iymax /= 2; + area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); + } + } + + @Override + double getToleranceXY() { + return m_toleranceXY; + } + + @Override + int getRasterSize() { + return m_width * m_scanLineSize; + } + + @Override + boolean dbgSaveToBitmap(String fileName) { + try { + FileOutputStream outfile = new FileOutputStream(fileName); + + int height = m_width; + int width = m_width; + int sz = 14 + 40 + 4 * m_width * height; + // Write the BITMAPFILEHEADER + ByteBuffer byteBuffer = ByteBuffer.allocate(sz); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + // byteBuffer.put((byte) 'M'); + byteBuffer.put((byte) 66); + byteBuffer.put((byte) 77); + // fwrite("BM", 1, 2, f); //bfType + byteBuffer.putInt(sz); + // fwrite(&sz, 1, 4, f);//bfSize + short zero16 = 0; + byteBuffer.putShort(zero16); + // fwrite(&zero16, 1, 2, f);//bfReserved1 + byteBuffer.putShort(zero16); + // fwrite(&zero16, 1, 2, f);//bfReserved2 + int offset = 14 + 40; + byteBuffer.putInt(offset); + // fwrite(&offset, 1, 4, f);//bfOffBits + + // Write the BITMAPINFOHEADER + int biSize = 40; + int biWidth = width; + int biHeight = -height; + short biPlanes = 1; + short biBitCount = 32; + int biCompression = 0; + int biSizeImage = 4 * width * height; + int biXPelsPerMeter = 0; + int biYPelsPerMeter = 0; + int biClrUsed = 0; + int biClrImportant = 0; + byteBuffer.putInt(biSize); + byteBuffer.putInt(biWidth); + byteBuffer.putInt(biHeight); + byteBuffer.putShort(biPlanes); + byteBuffer.putShort(biBitCount); + byteBuffer.putInt(biCompression); + byteBuffer.putInt(biSizeImage); + byteBuffer.putInt(biXPelsPerMeter); + byteBuffer.putInt(biYPelsPerMeter); + byteBuffer.putInt(biClrUsed); + byteBuffer.putInt(biClrImportant); + + int colors[] = { 0xFFFFFFFF, 0xFF000000, 0xFFFF0000, 0xFF00FF00 }; + // 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; + byteBuffer.putInt(colors[res]); + } + } + + byte[] b = byteBuffer.array(); + outfile.write(b); + outfile.close(); + return true; + } catch (IOException ex) { + return false; + + } } } diff --git a/src/com/esri/core/geometry/SimpleRasterizer.java b/src/com/esri/core/geometry/SimpleRasterizer.java new file mode 100644 index 00000000..3ca4d304 --- /dev/null +++ b/src/com/esri/core/geometry/SimpleRasterizer.java @@ -0,0 +1,377 @@ +/* + Copyright 2013 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.Collections; +import java.util.Comparator; + +/** + * Simple scanline rasterizer. Caller provides a callback to draw pixels to actual surface. + * + */ +public class SimpleRasterizer { + + /** + * Even odd fill rule + */ + public final static int EVEN_ODD = 0; + /** + * Winding fill rule + */ + public final static int WINDING = 1; + + 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 + */ + public abstract void drawScan(int y, int x, int numPxls); + } + + public SimpleRasterizer() { + width_ = -1; + height_ = -1; + } + + /** + * Sets up the rasterizer. + */ + public void setup(int width, int height, ScanCallback callback) + { + width_ = width; height_ = height; + ySortedEdges_ = null; + activeEdgesTable_ = null; + numEdges_ = 0; + callback_ = callback; + startAddingEdges(); + } + + public int getWidth() { + return width_; + } + + public int getHeight() { + return height_; + } + + /** + * Adds edges of a triangle. + */ + public 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[]) { + for (int i = 2; i < xy.length; i += 2) { + addEdge(xy[i-2], xy[i - 1], xy[i], xy[i + 1]); + } + } + + /** + * 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); + */ + public void startAddingEdges() { + if (numEdges_ > 0) { + ySortedEdges_ = null; + activeEdgesTable_ = null; + } + + minY_ = height_; + maxY_ = -1; + numEdges_ = 0; + } + + /** + * Renders all edges added so far, and removes them. + * @param fillMode + */ + public void renderEdges(int fillMode) { + evenOdd_ = fillMode == EVEN_ODD; + for (int line = minY_; line <= maxY_; line++) { + advanceAET_(); + addNewEdgesToAET_(line); + emitScans_(); + } + + numEdges_ = 0; + if (activeEdgesTable_ != null) + activeEdgesTable_.clear(); + + startAddingEdges();//reset for new edges + } + + /** + * Add a single edge. + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ + public void addEdge(double x1, double y1, double x2, double y2) { + if (y1 == y2) + return; + int dir = 1; + if (y1 > y2) { + double temp; + temp = x1; x1 = x2; x2 = temp; + temp = y1; y1 = y2; y2 = temp; + dir = -1; + } + + if (y2 < 0 || y1 >= height_) + return; + + if (x1 < 0 && x2 < 0) + { + x1 = -1; x2 = -1; + } + else if (x1 >= width_ && x2 >= width_) + { + x1 = width_; x2 = width_; + } + + //clip to extent + double dxdy = (x2 - x1) / (y2 - y1); + + if (y2 > height_) { + y2 = height_; + x2 = dxdy * (y2 - y1) + x1; + } + + if (y1 < 0) { + x1 = dxdy * (0 - y1) + x1; + 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) { + + y1 = (0 - x1) / dxdy + y1; + x1 = 0; + } + else if (x1 > bigX) { + //we know that dx != 0, otherwise it would return earlier + y1 = (width_ - x1) / dxdy + y1; + x1 = width_; + } + + if (x2 < -0x7fffff) { + //we know that dx != 0, otherwise it would return earlier + y2 = (0 - x1) / dxdy + y1; + x2 = 0; + } + else if (x2 > bigX) { + //we know that dx != 0, otherwise it would return earlier + y2 = (width_ - x1) / dxdy + y1; + x2 = width_; + } + + int ystart = (int)y1; + int yend = (int)y2; + if (ystart == yend) + return; + + Edge e; + if (recycledEdges_ != null && recycledEdges_.size() > 0) + e = recycledEdges_.remove(recycledEdges_.size() - 1); + else + e = new Edge(); + + e.x = (long)(x1 * 4294967296.0); + e.y = ystart; + e.ymax = yend; + e.dxdy = (long)(dxdy * 4294967296.0); + e.dir = dir; + + if (ySortedEdges_ == null) { + ySortedEdges_ = new ArrayList>(); + ySortedEdges_.ensureCapacity(height_); + for (int i = 0; i < height_; i++) { + ySortedEdges_.add(null); + } + } + + if (ySortedEdges_.get(e.y) == null) { + ySortedEdges_.set(e.y, new ArrayList()); + } + + ySortedEdges_.get(e.y).add(e); + if (e.y < minY_) + minY_ = e.y; + + if (e.ymax > maxY_) + maxY_ = e.ymax; + } + + class Edge { + long x; + long dxdy; + int y; + int ymax; + int dir; + } + + 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; + } + + e.x += e.dxdy; + } + } + } + + private void addNewEdgesToAET_(int y) { + if (y >= ySortedEdges_.size()) + return; + + if (activeEdgesTable_ == null) + activeEdgesTable_ = new ArrayList(); + + ArrayList edgesOnLine = ySortedEdges_.get(y); + if (edgesOnLine != null) { + for (int i = 0, n = edgesOnLine.size(); i < n; i++) { + activeEdgesTable_.add(edgesOnLine.get(i)); + } + + edgesOnLine.clear(); + } + } + + 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) + return; + + int w = 0; + Edge e0 = activeEdgesTable_.get(0); + int x0 = (int)(e0.x >> 32); + for (int i = 1; i < activeEdgesTable_.size(); i++) { + Edge e = activeEdgesTable_.get(i); + if (evenOdd_) + w ^= 1; + else + w += e.dir; + + if (e.x > e0.x) { + int x = (int)(e.x >> 32); + if (w == 1) { + int xx0 = snap_(x0, 0, width_); + int xx = snap_(x, 0, width_); + if (xx > xx0 && xx0 < width_) { + callback_.drawScan(e.y, xx0, xx - xx0); + } + } + + e0 = e; + x0 = x; + } + } + } + + static 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; + + return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0; + } + } + + private static EdgeComparator edgeCompare_ = new EdgeComparator(); + + private void sortAET_() { + if (!checkAETIsSorted_()) + { + Collections.sort(activeEdgesTable_, edgeCompare_); + while (activeEdgesTable_.size() > 0 && activeEdgesTable_.get(activeEdgesTable_.size() - 1) == null) + activeEdgesTable_.remove(activeEdgesTable_.size() - 1); + } + } + + 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; + } + e0 = e; + } + + return true; + } + + private ArrayList recycledEdges_; + private ArrayList activeEdgesTable_; + private ArrayList> ySortedEdges_; + public ScanCallback callback_; + private int width_; + private int height_; + private int minY_; + private int maxY_; + private int numEdges_; + private boolean evenOdd_; +} diff --git a/src/com/esri/core/geometry/Simplificator.java b/src/com/esri/core/geometry/Simplificator.java index 1c6db3ce..88760528 100644 --- a/src/com/esri/core/geometry/Simplificator.java +++ b/src/com/esri/core/geometry/Simplificator.java @@ -512,7 +512,8 @@ private boolean _simplify() { coincidentCount = 0; } - vlistindex = m_sortedVertices.getNext(vlistindex); + if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above + vlistindex = m_sortedVertices.getNext(vlistindex); } m_nextVertexToProcess = -1; diff --git a/src/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/com/esri/core/geometry/StridedIndexTypeCollection.java index 90bfc9d5..ca631d98 100644 --- a/src/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -95,7 +95,14 @@ int newElement() { int element = m_firstFree; if (element == -1) { if (m_last == m_capacity) { - grow_(m_capacity != 0 ? ((m_capacity + 1) * 3 / 2) : 1); + long newcap = m_capacity != 0 ? (((long)m_capacity + 1) * 3 / 2) : (long)1; + if (newcap > Integer.MAX_VALUE) + newcap = Integer.MAX_VALUE;//cannot grow past 2gb elements presently + + if (newcap == m_capacity) + throw new IndexOutOfBoundsException(); + + grow_(newcap); } element = ((m_last / m_blockSize) << m_blockPower) @@ -195,22 +202,25 @@ private boolean dbgdelete_(int element) { final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; - private void grow_(int newsize) { + private void grow_(long newsize) { if (m_buffer == null) { m_buffer = new ArrayList(); } assert (newsize > m_capacity); - int nblocks = (newsize + m_blockSize - 1) / m_blockSize; - m_buffer.ensureCapacity(nblocks); + long nblocks = (newsize + m_blockSize - 1) / m_blockSize; + if (nblocks > Integer.MAX_VALUE) + throw new IndexOutOfBoundsException(); + + m_buffer.ensureCapacity((int)nblocks); if (nblocks == 1) { // When less than one block is needed we allocate smaller arrays // than m_realBlockSize to avoid initialization cost. int oldsz = m_capacity > 0 ? m_capacity : 0; assert (oldsz < newsize); int i = 0; - int realnewsize = newsize * m_realStride; + int realnewsize = (int)newsize * m_realStride; while (realnewsize > st_sizes[i]) // get the size to allocate. Using fixed sizes to reduce // fragmentation. @@ -225,6 +235,9 @@ private void grow_(int newsize) { } m_capacity = b.length / m_realStride; } else { + if (nblocks * m_blockSize > Integer.MAX_VALUE) + throw new IndexOutOfBoundsException(); + if (m_buffer.size() == 1) { if (m_buffer.get(0).length < m_realBlockSize) { // resize the first buffer to ensure it is equal the diff --git a/unittest/com/esri/core/geometry/TestDistance.java b/unittest/com/esri/core/geometry/TestDistance.java index 9fc159d2..b531bb17 100644 --- a/unittest/com/esri/core/geometry/TestDistance.java +++ b/unittest/com/esri/core/geometry/TestDistance.java @@ -62,6 +62,14 @@ public static void testDistanceBetweenTriangles() { assertTrue(0.0 < distance && distance < xSeparation + ySeparation); } + @Test + public static void testDistanceBetweenPointAndEnvelope() { + Envelope env = new Envelope(23,23, 23,23); + Point pt = new Point(30, 30); + double dist = GeometryEngine.distance(env, pt, null); // expect just under 10. + assertTrue(Math.abs(dist - 9.8994949) < 0.0001); + } + @Test public static void testDistanceBetweenHugeGeometries() { /* const */int N = 1000; // Should be even diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/unittest/com/esri/core/geometry/TestIntersection.java index 68b0766b..66e0bc49 100644 --- a/unittest/com/esri/core/geometry/TestIntersection.java +++ b/unittest/com/esri/core/geometry/TestIntersection.java @@ -991,5 +991,15 @@ public void testUnionTickTock() { Geometry result2 = res.next(); assertTrue(result.equals(result2)); } - + + @Test + public void testIntersectionIssueLinePoly1() { + String wkt1 = new String("polygon((0 0, 10 0, 10 10, 0 10, 0 0))"); + String wkt2 = new String("linestring(9 5, 10 5, 9 4, 8 3)"); + Geometry g1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt1, null); + Geometry g2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt2, null); + Geometry res = OperatorIntersection.local().execute(g1, g2, null, null); + assertTrue(((Polyline)res).getPathCount() == 1); + assertTrue(((Polyline)res).getPointCount() == 4); + } } diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index 3099767a..816813bd 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -704,14 +704,27 @@ public void testIsectTria1() { String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; OGCGeometry g0 = OGCGeometry.fromText(wkt); OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(null); - g1.setSpatialReference(null); + g0.setSpatialReference(SpatialReference.create(4326)); + g1.setSpatialReference(SpatialReference.create(4326)); OGCGeometry rslt = g0.intersection(g1); assertTrue(rslt != null); assertTrue(rslt.geometryType().equals("Polygon")); + assertTrue(rslt.esriSR.getID() == 4326); String s = rslt.asText(); } + public void testIsectTriaJson1() throws JsonParseException, IOException { + 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); + OGCGeometry g1 = OGCGeometry.fromJson(json2); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.geometryType().equals("Polygon")); + assertTrue(rslt.esriSR.getID() == 4326); + String s = GeometryEngine.geometryToJson(rslt.getEsriSpatialReference().getID(), rslt.getEsriGeometry()); + } + public void testIsectTria2() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; @@ -731,12 +744,13 @@ public void testIsectTria3() { String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; OGCGeometry g0 = OGCGeometry.fromText(wkt); OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(null); - g1.setSpatialReference(null); + g0.setSpatialReference(SpatialReference.create(4326)); + g1.setSpatialReference(SpatialReference.create(4326)); OGCGeometry rslt = g0.intersection(g1); assertTrue(rslt != null); assertTrue(rslt.dimension() == 0); assertTrue(rslt.geometryType().equals("Point")); + assertTrue(rslt.esriSR.getID() == 4326); String s = rslt.asText(); } diff --git a/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java b/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java new file mode 100644 index 00000000..a96528ca --- /dev/null +++ b/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -0,0 +1,134 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + +public class TestRasterizedGeometry2D extends TestCase { + boolean rgHelper(RasterizedGeometry2D rg, MultiPath mp) { + SegmentIterator iter = mp.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + int count = 20; + + for (int i = 0; i < count; i++) { + double t = (1.0 * i / count); + Point2D pt = seg.getCoord2D(t); + RasterizedGeometry2D.HitType hit = rg.queryPointInGeometry( + pt.x, pt.y); + if (hit != RasterizedGeometry2D.HitType.Border) + return false; + } + } + } + + if (mp.getType() != Geometry.Type.Polygon) + return true; + + Polygon poly = (Polygon) mp; + Envelope2D env = new Envelope2D(); + poly.queryEnvelope2D(env); + int count = 100; + for (int iy = 0; iy < count; iy++) { + double ty = 1.0 * iy / count; + double y = env.ymin * (1.0 - ty) + ty * env.ymax; + for (int ix = 0; ix < count; ix++) { + double tx = 1.0 * ix / count; + double x = env.xmin * (1.0 - tx) + tx * env.xmax; + + RasterizedGeometry2D.HitType hit = rg + .queryPointInGeometry(x, y); + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( + poly, new Point2D(x, y), 0); + if (res == PolygonUtils.PiPResult.PiPInside) { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Inside); + if (!bgood) + return false; + } else if (res == PolygonUtils.PiPResult.PiPOutside) { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Outside); + if (!bgood) + return false; + } else { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border); + if (!bgood) + return false; + } + } + } + + return true; + } + + @Test + public void test() { + { + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(100, 10); + poly.lineTo(100, 100); + poly.lineTo(10, 100); + + // create using move semantics. Usually we do not use this + // approach. + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(poly, 0, 1024); + //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + RasterizedGeometry2D.HitType res; + res = rg.queryPointInGeometry(7, 10); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(10, 10); + assertTrue(res == RasterizedGeometry2D.HitType.Border); + res = rg.queryPointInGeometry(50, 50); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + + assertTrue(rgHelper(rg, poly)); + } + + { + Polygon poly = new Polygon(); + // create a star (non-simple) + poly.startPath(1, 0); + poly.lineTo(5, 10); + poly.lineTo(9, 0); + poly.lineTo(0, 6); + poly.lineTo(10, 6); + + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(poly, 0, 1024); + //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + RasterizedGeometry2D.HitType res; + res = rg.queryPointInGeometry(5, 5.5); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(5, 8); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + res = rg.queryPointInGeometry(1.63, 0.77); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + res = rg.queryPointInGeometry(1, 3); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(1.6, 0.1); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + assertTrue(rgHelper(rg, poly)); + } + + { + Polygon poly = new Polygon(); + // create a star (non-simple) + poly.startPath(1, 0); + poly.lineTo(5, 10); + poly.lineTo(9, 0); + poly.lineTo(0, 6); + poly.lineTo(10, 6); + + SpatialReference sr = SpatialReference.create(4326); + poly = (Polygon)OperatorSimplify.local().execute(poly, sr, true, null); + OperatorContains.local().accelerateGeometry(poly, sr, GeometryAccelerationDegree.enumMedium); + assertFalse(OperatorContains.local().execute(poly, new Point(5, 5.5), sr, null)); + assertTrue(OperatorContains.local().execute(poly, new Point(5, 8), sr, null)); + assertTrue(OperatorContains.local().execute(poly, new Point(1.63, 0.77), sr, null)); + assertFalse(OperatorContains.local().execute(poly, new Point(1, 3), sr, null)); + assertFalse(OperatorContains.local().execute(poly, new Point(1.6, 0.1), sr, null)); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestSerialization.java b/unittest/com/esri/core/geometry/TestSerialization.java index 27587c34..289546da 100644 --- a/unittest/com/esri/core/geometry/TestSerialization.java +++ b/unittest/com/esri/core/geometry/TestSerialization.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -291,4 +292,47 @@ public void testSerializeSR() { 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/unittest/com/esri/core/geometry/savedEnvelope2D.txt b/unittest/com/esri/core/geometry/savedEnvelope2D.txt new file mode 100644 index 0000000000000000000000000000000000000000..354682bc0804cf4992b71e4e7b8fecc74e164727 GIT binary patch literal 115 zcmZ4UmVvdnh(R$qKUXicxF}OEIlm}XFFiFsH?^dwQqMK7EHx*;Al1l)0RkAA8CYBx zSSoT8E5KA{9+0Yp@G5gN^C}7)`YnFmR=V)iAx-)H+)cWC4&BH9e%|J~$RWMG=Iz9c G;tBw4Rw=9i literal 0 HcmV?d00001 From 6686af168ade721ac14bfac42fc11e379c2cb9c0 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 9 Jan 2014 10:14:51 -0800 Subject: [PATCH 008/145] Update JsonReader.java Inline fix for JSONArrayEnumerator.getCurrentObject() - use JSONArray.opt instead of JSONArray.get --- src/com/esri/core/geometry/JsonReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java index 10e4d0c7..342c6435 100644 --- a/src/com/esri/core/geometry/JsonReader.java +++ b/src/com/esri/core/geometry/JsonReader.java @@ -352,7 +352,7 @@ Object getCurrentObject() { throw new GeometryException("invalid call"); } - return m_jsonArray.get(m_currentIndex); + return m_jsonArray.opt(m_currentIndex); } boolean next() { From 38c70f7afd8180374908315b5736d228243fd8ac Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 23 Jan 2014 09:34:29 -0800 Subject: [PATCH 009/145] marked setXY public as well as some other methods --- src/com/esri/core/geometry/Envelope1D.java | 4 ++-- src/com/esri/core/geometry/Envelope2D.java | 6 +++--- src/com/esri/core/geometry/MultiPath.java | 8 ++++---- src/com/esri/core/geometry/MultiPoint.java | 8 ++++---- .../core/geometry/MultiVertexGeometry.java | 18 +++++++++--------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope1D.java b/src/com/esri/core/geometry/Envelope1D.java index e0d19408..5d982bc1 100644 --- a/src/com/esri/core/geometry/Envelope1D.java +++ b/src/com/esri/core/geometry/Envelope1D.java @@ -48,7 +48,7 @@ public void setCoords(double _vmin, double _vmax) { normalize(); } - void normalize() { + public void normalize() { if (NumberUtils.isNaN(vmin)) return; if (vmin > vmax) { @@ -175,7 +175,7 @@ void setCoordsNoNaN_(double vmin_, double vmax_) { normalizeNoNaN_(); } - double snapClip(double v) /* const */ + public double snapClip(double v) /* const */ { return NumberUtils.snap(v, vmin, vmax); } diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index 30aa184b..89dded08 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -557,7 +557,7 @@ public boolean contains(Envelope2D other) {// Note: Will return False, if /** * Returns True if the envelope contains the point (boundary exclusive). */ - boolean containsExclusive(double x, double y) { + public boolean containsExclusive(double x, double y) { // Note: This will return False, if envelope is empty, thus no need to // call is_empty(). return x > xmin && x < xmax && y > ymin && y < ymax; @@ -566,7 +566,7 @@ boolean containsExclusive(double x, double y) { /** * Returns True if the envelope contains the point (boundary exclusive). */ - boolean containsExclusive(Point2D pt) { + public boolean containsExclusive(Point2D pt) { return contains(pt.x, pt.y); } @@ -978,7 +978,7 @@ boolean clipLineAuxiliary(double denominator, double numerator, * Returns True, envelope is degenerate (Width or Height are less than * tolerance). Note: this returns False for Empty envelope. */ - boolean isDegenerate(double tolerance) { + public boolean isDegenerate(double tolerance) { return !isEmpty() && (getWidth() <= tolerance || getHeight() <= tolerance); } diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java index eca929d6..34cae2b0 100644 --- a/src/com/esri/core/geometry/MultiPath.java +++ b/src/com/esri/core/geometry/MultiPath.java @@ -124,12 +124,12 @@ public Point2D getXY(int index) { } @Override - void getXY(int index, Point2D pt) { + public void getXY(int index, Point2D pt) { m_impl.getXY(index, pt); } @Override - void setXY(int index, Point2D pt) { + public void setXY(int index, Point2D pt) { m_impl.setXY(index, pt); } @@ -184,7 +184,7 @@ Geometry getBoundary() { } @Override - void queryCoordinates(Point2D[] dst) { + public void queryCoordinates(Point2D[] dst) { m_impl.queryCoordinates(dst); } @@ -194,7 +194,7 @@ void queryCoordinates(Point3D[] dst) { } @Override - void queryCoordinates(Point[] dst) { + public void queryCoordinates(Point[] dst) { m_impl.queryCoordinates(dst); } diff --git a/src/com/esri/core/geometry/MultiPoint.java b/src/com/esri/core/geometry/MultiPoint.java index 0a7baf04..008401fd 100644 --- a/src/com/esri/core/geometry/MultiPoint.java +++ b/src/com/esri/core/geometry/MultiPoint.java @@ -76,7 +76,7 @@ public Point2D getXY(int index) { } @Override - void getXY(int index, Point2D pt) { + public void getXY(int index, Point2D pt) { m_impl.getXY(index, pt); } @@ -86,12 +86,12 @@ Point3D getXYZ(int index) { } @Override - void queryCoordinates(Point2D[] dst) { + public void queryCoordinates(Point2D[] dst) { m_impl.queryCoordinates(dst); } @Override - void queryCoordinates(Point[] dst) { + public void queryCoordinates(Point[] dst) { m_impl.queryCoordinates(dst); } @@ -215,7 +215,7 @@ public void setPoint(int index, Point pointSrc) { } @Override - void setXY(int index, Point2D pt) { + public void setXY(int index, Point2D pt) { m_impl.setXY(index, pt); } diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java index b8102c0c..07730838 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -32,7 +32,7 @@ * The vertex attributes are stored in separate arrays of corresponding type. * There are as many arrays as there are attributes in the vertex. */ -abstract class MultiVertexGeometry extends Geometry implements +public abstract class MultiVertexGeometry extends Geometry implements Serializable { @Override @@ -80,13 +80,13 @@ public void getPoint(int index, Point ptOut) { */ public abstract Point2D getXY(int index); - abstract void getXY(int index, Point2D pt); + public abstract void getXY(int index, Point2D pt); /** * Sets XY coordinates of the given vertex of the Geometry. All other * attributes are unchanged. */ - abstract void setXY(int index, Point2D pt); + public abstract void setXY(int index, Point2D pt); /** * Returns XYZ coordinates of the given vertex of the Geometry. If the @@ -119,13 +119,13 @@ Point3D[] getCoordinates3D() { return arr; } - abstract void queryCoordinates(Point[] dst); + public abstract void queryCoordinates(Point[] dst); /** * Queries XY coordinates as an array. The array must be larg enough (See * GetPointCount()). */ - abstract void queryCoordinates(Point2D[] dst); + public abstract void queryCoordinates(Point2D[] dst); /** * Queries XYZ coordinates as an array. The array must be larg enough (See @@ -147,7 +147,7 @@ Point3D[] getCoordinates3D() { * If attribute is not present, the default value is returned. * See VertexDescription::GetDefaultValue() method. */ - public abstract double getAttributeAsDbl(int semantics, int index, + abstract double getAttributeAsDbl(int semantics, int index, int ordinate); /** @@ -165,7 +165,7 @@ public abstract double getAttributeAsDbl(int semantics, int index, * See VertexDescription::GetDefaultValue() method. Avoid using * this method on non-integer atributes. */ - public abstract int getAttributeAsInt(int semantics, int index, int ordinate); + abstract int getAttributeAsInt(int semantics, int index, int ordinate); /** * Sets the value of given attribute at given posisiotnsis. @@ -183,7 +183,7 @@ public abstract double getAttributeAsDbl(int semantics, int index, * * If the attribute is not present in this Geometry, it is added. */ - public abstract void setAttribute(int semantics, int index, int ordinate, + abstract void setAttribute(int semantics, int index, int ordinate, double value); /** @@ -191,7 +191,7 @@ public abstract void setAttribute(int semantics, int index, int ordinate, * non-integer atributes because some double attributes may have NaN default * values (e.g. Ms) */ - public abstract void setAttribute(int semantics, int index, int ordinate, + abstract void setAttribute(int semantics, int index, int ordinate, int value); /** From 81d8ad0257e1db4d7094af79fa8010f9d059c88d Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 23 Jan 2014 09:39:17 -0800 Subject: [PATCH 010/145] remove comments --- src/com/esri/core/geometry/MultiVertexGeometry.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java index 07730838..51948f74 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -40,7 +40,6 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { throw new GeometryException("invalid call"); } - // Multipart methods: /** * Returns the total vertex count in this Geometry. */ @@ -56,7 +55,7 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { */ public void getPoint(int index, Point ptOut) { getPointByVal(index, ptOut); - }// Java only + } /** * Sets the vertex at given index of the Geometry. From f78dbed42f1b231ae83f6de37e68f19ecdc044ee Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 23 Jan 2014 10:03:33 -0800 Subject: [PATCH 011/145] marked two methods on Point2D public --- src/com/esri/core/geometry/Point2D.java | 48 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java index fe1a13b9..319cd5dd 100644 --- a/src/com/esri/core/geometry/Point2D.java +++ b/src/com/esri/core/geometry/Point2D.java @@ -250,9 +250,7 @@ boolean _isNan() { // between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. // Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); // 3 : [180 : 270); 4 : [270 : 360) - int _getQuarter() { - // _ASSERT(x != 0 || y != 0 || !NumberUtils.isNaN(x) || - // !NumberUtils.isNaN(y)); + final int _getQuarter() { if (x > 0) { if (y >= 0) return 1; // x > 0 && y <= 0 @@ -270,18 +268,29 @@ int _getQuarter() { } } + /** + * Calculates which quarter of XY plane the vector lies in. First quarter is + * between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. + * The quarters are numbered counterclockwise. + * Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); + * 3 : [180 : 270); 4 : [270 : 360) + */ + public int getQuarter() { return _getQuarter(); } + // Assume vector v1 and v2 have same origin. The function compares the - // vectors by angle from the x acis to the vector in the counter clockwise + // vectors by angle from the x axis to the vector in the counter clockwise // direction. - // > > - // \ / + // > > + // \ / // V3 \ / V1 - // ----------------/-------------------->X In this example, - // __compareVectors(V1, V2) == -1. - // \ _compareVectors(V1, V3) == -1 - // \ V2 _compareVectors(V2, V3) == 1 - // > - static int _compareVectors(Point2D v1, Point2D v2) { + // \ + // \ + // >V2 + // _compareVectors(V1, V2) == -1. + // _compareVectors(V1, V3) == -1 + // _compareVectors(V2, V3) == 1 + // + final static int _compareVectors(Point2D v1, Point2D v2) { int q1 = v1._getQuarter(); int q2 = v2._getQuarter(); @@ -292,6 +301,21 @@ static int _compareVectors(Point2D v1, Point2D v2) { return q1 < q2 ? -1 : 1; } + /** + * Assume vector v1 and v2 have same origin. The function compares the + * vectors by angle in the counter clockwise direction from the axis X. + * + * For example, V1 makes 30 degree angle counterclockwise from horizontal x axis + * V2, makes 270, V3 makes 90, then + * compareVectors(V1, V2) == -1. + * compareVectors(V1, V3) == -1. + * compareVectors(V2, V3) == 1. + * @return Returns 1 if v1 is less than v2, 0 if equal, and 1 if greater. + */ + public static int compareVectors(Point2D v1, Point2D v2) { + return _compareVectors(v1, v2); + } + static class CompareVectors implements Comparator { @Override public int compare(Point2D v1, Point2D v2) { From 3b807a1d9c0d9072694835d2cc13f3c57fd4961c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 10 Feb 2014 12:40:06 -0800 Subject: [PATCH 012/145] make RasterizedGeometry2D public --- .../core/geometry/RasterizedGeometry2D.java | 20 +++++++++---------- .../geometry/RasterizedGeometry2DImpl.java | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/com/esri/core/geometry/RasterizedGeometry2D.java index 54ca904b..0e420658 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2D.java @@ -25,7 +25,7 @@ import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; -abstract class RasterizedGeometry2D { +public abstract class RasterizedGeometry2D { public enum HitType { Outside(0), // the test geometry is well outside the geometry bounds @@ -33,7 +33,7 @@ public enum HitType { Border(2); // the test geometry is close to the bounds or intersects the // bounds - private int enumVal; + int enumVal; private HitType(int val) { enumVal = val; @@ -41,7 +41,7 @@ private HitType(int val) { } /** - * Test a point agains the RasterizedGeometry + * Test a point against the RasterizedGeometry */ public abstract HitType queryPointInGeometry(double x, double y); @@ -54,11 +54,11 @@ private HitType(int val) { * Creates a rasterized geometry from a given Geometry. * * @param geom - * The input geometry to rasterize. + * The input geometry to rasterize. It has to be a MultiVertexGeometry instance. * @param toleranceXY * The tolerance of the rasterization. Raster pixels that are * closer than given tolerance to the Geometry will be set. - * @param rasterSize + * @param rasterSizeBytes * The max size of the raster in bytes. The raster has size of * rasterSize x rasterSize. Polygons are rasterized into 2 bpp * (bits per pixel) rasters while other geometries are rasterized @@ -75,7 +75,7 @@ public static RasterizedGeometry2D create(Geometry geom, return (RasterizedGeometry2D) gc; } - public static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, + static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, double toleranceXY, int rasterSizeBytes) { if (!canUseAccelerator(geom)) throw new IllegalArgumentException(); @@ -121,19 +121,19 @@ static boolean canUseAccelerator(Geometry geom) { /** * Returns the tolerance for which the rasterized Geometry has been built. */ - abstract double getToleranceXY(); + public abstract double getToleranceXY(); /** * Returns raster size in bytes */ - abstract int getRasterSize(); + public abstract int getRasterSize(); /** - * Dumps the raster to file for debug purposes. + * Dumps the raster to a bmp file for debug purposes. * * @param fileName * @returns true if success, false otherwise. */ - abstract boolean dbgSaveToBitmap(String fileName); + public abstract boolean dbgSaveToBitmap(String fileName); } diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java index cd9f445f..b2ebf125 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -500,17 +500,17 @@ else if (res == 1) } @Override - double getToleranceXY() { + public double getToleranceXY() { return m_toleranceXY; } @Override - int getRasterSize() { + public int getRasterSize() { return m_width * m_scanLineSize; } @Override - boolean dbgSaveToBitmap(String fileName) { + public boolean dbgSaveToBitmap(String fileName) { try { FileOutputStream outfile = new FileOutputStream(fileName); From b7e6972e7636408ec15a41adbae0e2b9e65386f4 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 10 Feb 2014 16:24:01 -0800 Subject: [PATCH 013/145] local build cannot handle several classes in one java file --- .../core/geometry/JSONArrayEnumerator.java | 67 +++ .../core/geometry/JSONObjectEnumerator.java | 80 ++++ .../esri/core/geometry/JsonParserReader.java | 71 ++++ src/com/esri/core/geometry/JsonReader.java | 321 -------------- .../esri/core/geometry/JsonStringWriter.java | 398 ++++++++++++++++++ .../esri/core/geometry/JsonValueReader.java | 228 ++++++++++ src/com/esri/core/geometry/JsonWriter.java | 372 ---------------- .../core/geometry/RasterizedGeometry2D.java | 2 +- 8 files changed, 845 insertions(+), 694 deletions(-) create mode 100644 src/com/esri/core/geometry/JSONArrayEnumerator.java create mode 100644 src/com/esri/core/geometry/JSONObjectEnumerator.java create mode 100644 src/com/esri/core/geometry/JsonParserReader.java create mode 100644 src/com/esri/core/geometry/JsonStringWriter.java create mode 100644 src/com/esri/core/geometry/JsonValueReader.java diff --git a/src/com/esri/core/geometry/JSONArrayEnumerator.java b/src/com/esri/core/geometry/JSONArrayEnumerator.java new file mode 100644 index 00000000..5b2d93d7 --- /dev/null +++ b/src/com/esri/core/geometry/JSONArrayEnumerator.java @@ -0,0 +1,67 @@ +/* + Copyright 1995-2013 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/com/esri/core/geometry/JSONObjectEnumerator.java b/src/com/esri/core/geometry/JSONObjectEnumerator.java new file mode 100644 index 00000000..f474a16c --- /dev/null +++ b/src/com/esri/core/geometry/JSONObjectEnumerator.java @@ -0,0 +1,80 @@ +/* + Copyright 1995-2013 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 JSONObjectEnumerator { + + private JSONObject m_jsonObject; + private boolean m_bStarted; + private int m_currentIndex; + private String[] m_keys; + + JSONObjectEnumerator(JSONObject jsonObject) { + m_bStarted = false; + m_currentIndex = -1; + m_jsonObject = jsonObject; + } + + String getCurrentKey() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_keys[m_currentIndex]; + } + + Object getCurrentObject() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_jsonObject.opt(m_keys[m_currentIndex]); + } + + boolean next() { + if (!m_bStarted) { + m_currentIndex = 0; + m_keys = JSONObject.getNames(m_jsonObject); + m_bStarted = true; + } else if (m_currentIndex != m_jsonObject.length()) { + m_currentIndex++; + } + + return m_currentIndex != m_jsonObject.length(); + } +} diff --git a/src/com/esri/core/geometry/JsonParserReader.java b/src/com/esri/core/geometry/JsonParserReader.java new file mode 100644 index 00000000..efa6f55d --- /dev/null +++ b/src/com/esri/core/geometry/JsonParserReader.java @@ -0,0 +1,71 @@ +/* + Copyright 1995-2013 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 JsonParserReader extends JsonReader { + + private JsonParser m_jsonParser; + + JsonParserReader(JsonParser jsonParser) { + m_jsonParser = jsonParser; + } + + @Override + JsonToken nextToken() throws Exception { + JsonToken token = m_jsonParser.nextToken(); + return token; + } + + @Override + JsonToken currentToken() throws Exception { + return m_jsonParser.getCurrentToken(); + } + + @Override + void skipChildren() throws Exception { + m_jsonParser.skipChildren(); + } + + @Override + String currentString() throws Exception { + return m_jsonParser.getText(); + } + + @Override + double currentDoubleValue() throws Exception { + return m_jsonParser.getValueAsDouble(); + } + + @Override + int currentIntValue() throws Exception { + return m_jsonParser.getValueAsInt(); + } +} + diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java index 342c6435..1b3a4f03 100644 --- a/src/com/esri/core/geometry/JsonReader.java +++ b/src/com/esri/core/geometry/JsonReader.java @@ -44,325 +44,4 @@ abstract class JsonReader { abstract int currentIntValue() throws Exception; } -final class JsonParserReader extends JsonReader { - private JsonParser m_jsonParser; - - JsonParserReader(JsonParser jsonParser) { - m_jsonParser = jsonParser; - } - - @Override - JsonToken nextToken() throws Exception { - JsonToken token = m_jsonParser.nextToken(); - return token; - } - - @Override - JsonToken currentToken() throws Exception { - return m_jsonParser.getCurrentToken(); - } - - @Override - void skipChildren() throws Exception { - m_jsonParser.skipChildren(); - } - - @Override - String currentString() throws Exception { - return m_jsonParser.getText(); - } - - @Override - double currentDoubleValue() throws Exception { - return m_jsonParser.getValueAsDouble(); - } - - @Override - int currentIntValue() throws Exception { - return m_jsonParser.getValueAsInt(); - } -} - -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 Exception { - 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 Exception { - return m_currentToken; - } - - @Override - void skipChildren() throws Exception { - 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 Exception { - 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 Exception { - 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 Exception { - if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).intValue(); - } -} - -final class JSONObjectEnumerator { - - private JSONObject m_jsonObject; - private boolean m_bStarted; - private int m_currentIndex; - private String[] m_keys; - - JSONObjectEnumerator(JSONObject jsonObject) { - m_bStarted = false; - m_currentIndex = -1; - m_jsonObject = jsonObject; - } - - String getCurrentKey() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { - throw new GeometryException("invalid call"); - } - - return m_keys[m_currentIndex]; - } - - Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { - throw new GeometryException("invalid call"); - } - - return m_jsonObject.opt(m_keys[m_currentIndex]); - } - - boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_keys = JSONObject.getNames(m_jsonObject); - m_bStarted = true; - } else if (m_currentIndex != m_jsonObject.length()) { - m_currentIndex++; - } - - return m_currentIndex != m_jsonObject.length(); - } -} - -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/com/esri/core/geometry/JsonStringWriter.java b/src/com/esri/core/geometry/JsonStringWriter.java new file mode 100644 index 00000000..dd4cf251 --- /dev/null +++ b/src/com/esri/core/geometry/JsonStringWriter.java @@ -0,0 +1,398 @@ +/* + Copyright 1995-2013 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; + +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 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) { + 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('"'); + } +} + diff --git a/src/com/esri/core/geometry/JsonValueReader.java b/src/com/esri/core/geometry/JsonValueReader.java new file mode 100644 index 00000000..9f320029 --- /dev/null +++ b/src/com/esri/core/geometry/JsonValueReader.java @@ -0,0 +1,228 @@ +/* + Copyright 1995-2013 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 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 Exception { + 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 Exception { + return m_currentToken; + } + + @Override + void skipChildren() throws Exception { + 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 Exception { + 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 Exception { + 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 Exception { + if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { + throw new GeometryException("invalid call"); + } + + return ((Number) currentObject_()).intValue(); + } +} diff --git a/src/com/esri/core/geometry/JsonWriter.java b/src/com/esri/core/geometry/JsonWriter.java index 710a34d6..28e15874 100644 --- a/src/com/esri/core/geometry/JsonWriter.java +++ b/src/com/esri/core/geometry/JsonWriter.java @@ -88,375 +88,3 @@ protected interface State { } } -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 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) { - 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('"'); - } -} diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/com/esri/core/geometry/RasterizedGeometry2D.java index 0e420658..558c3b16 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2D.java @@ -132,7 +132,7 @@ static boolean canUseAccelerator(Geometry geom) { * Dumps the raster to a bmp file for debug purposes. * * @param fileName - * @returns true if success, false otherwise. + * @return true if success, false otherwise. */ public abstract boolean dbgSaveToBitmap(String fileName); From 7d1f9c8b859a6b939d5e7f3889289d3ca7380650 Mon Sep 17 00:00:00 2001 From: Dan O'Neill Date: Thu, 6 Mar 2014 11:40:17 -0900 Subject: [PATCH 014/145] added some ide project setting files to ignore --- .gitignore | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.gitignore b/.gitignore index 6d29d23b..82b5005e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,28 @@ esri-geometry-api.jar .classpath description.jardesc *.bak + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ + +# Eclipse project files +.settings/ +libs/ + +# SublimeText +/*.sublime-project +*.sublime-workspace + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db From 55ac0a734ae59dbc416f9157b2713907c96f8d89 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 15 May 2014 10:58:56 -0700 Subject: [PATCH 015/145] changed the folder structure --- .gitignore | 1 + DepFiles/unittest/jersey-client-1.5.jar | Bin 128096 -> 0 bytes DepFiles/unittest/jersey-core-1.5.jar | Bin 455665 -> 0 bytes build.xml | 33 ++++++++++++------ pom.xml | 28 +++++---------- .../core/geometry/AttributeStreamBase.java | 0 .../core/geometry/AttributeStreamOfDbl.java | 0 .../core/geometry/AttributeStreamOfFloat.java | 0 .../core/geometry/AttributeStreamOfInt16.java | 0 .../core/geometry/AttributeStreamOfInt32.java | 0 .../core/geometry/AttributeStreamOfInt64.java | 0 .../core/geometry/AttributeStreamOfInt8.java | 0 .../com/esri/core/geometry/Boundary.java | 0 .../com/esri/core/geometry/BucketSort.java | 0 .../com/esri/core/geometry/Bufferer.java | 0 .../esri/core/geometry/ByteBufferCursor.java | 0 .../com/esri/core/geometry/ClassicSort.java | 0 .../java}/com/esri/core/geometry/Clipper.java | 0 .../com/esri/core/geometry/Clusterer.java | 0 .../esri/core/geometry/ConstructOffset.java | 0 .../com/esri/core/geometry/ConvexHull.java | 0 .../esri/core/geometry/CrackAndCluster.java | 0 .../java}/com/esri/core/geometry/Cracker.java | 0 .../java}/com/esri/core/geometry/Cutter.java | 0 .../com/esri/core/geometry/DirtyFlags.java | 0 .../com/esri/core/geometry/ECoordinate.java | 0 .../com/esri/core/geometry/EditShape.java | 0 .../com/esri/core/geometry/Envelope.java | 0 .../com/esri/core/geometry/Envelope1D.java | 0 .../com/esri/core/geometry/Envelope2D.java | 0 .../geometry/Envelope2DIntersectorImpl.java | 0 .../com/esri/core/geometry/Envelope3D.java | 0 .../java}/com/esri/core/geometry/GeoDist.java | 0 .../core/geometry/GeoJsonExportFlags.java | 0 .../core/geometry/GeoJsonImportFlags.java | 0 .../esri/core/geometry/GeodeticCurveType.java | 0 .../com/esri/core/geometry/Geometry.java | 0 .../core/geometry/GeometryAccelerators.java | 0 .../esri/core/geometry/GeometryCursor.java | 0 .../core/geometry/GeometryCursorAppend.java | 0 .../esri/core/geometry/GeometryEngine.java | 0 .../esri/core/geometry/GeometryException.java | 0 .../core/geometry/GeometrySerializer.java | 0 .../esri/core/geometry/IndexHashTable.java | 0 .../esri/core/geometry/IndexMultiDCList.java | 0 .../esri/core/geometry/IndexMultiList.java | 0 .../com/esri/core/geometry/InternalUtils.java | 0 .../java}/com/esri/core/geometry/Interop.java | 0 .../esri/core/geometry/IntervalTreeImpl.java | 0 .../core/geometry/JSONArrayEnumerator.java | 0 .../core/geometry/JSONObjectEnumerator.java | 0 .../com/esri/core/geometry/JSONUtils.java | 0 .../com/esri/core/geometry/JsonCursor.java | 0 .../esri/core/geometry/JsonParserCursor.java | 0 .../esri/core/geometry/JsonParserReader.java | 0 .../com/esri/core/geometry/JsonReader.java | 0 .../esri/core/geometry/JsonStringWriter.java | 0 .../esri/core/geometry/JsonValueReader.java | 0 .../com/esri/core/geometry/JsonWriter.java | 0 .../java}/com/esri/core/geometry/Line.java | 0 .../geometry/ListeningGeometryCursor.java | 0 .../com/esri/core/geometry/MapGeometry.java | 0 .../esri/core/geometry/MapGeometryCursor.java | 0 .../esri/core/geometry/MapOGCStructure.java | 0 .../com/esri/core/geometry/MathUtils.java | 0 .../core/geometry/MgrsConversionMode.java | 0 .../com/esri/core/geometry/MultiPath.java | 0 .../com/esri/core/geometry/MultiPathImpl.java | 0 .../com/esri/core/geometry/MultiPoint.java | 0 .../esri/core/geometry/MultiPointImpl.java | 0 .../core/geometry/MultiVertexGeometry.java | 0 .../geometry/MultiVertexGeometryImpl.java | 0 .../esri/core/geometry/NonSimpleResult.java | 0 .../com/esri/core/geometry/NumberUtils.java | 0 .../com/esri/core/geometry/OGCStructure.java | 0 .../esri/core/geometry/ObjectCacheTable.java | 0 .../com/esri/core/geometry/Operator.java | 0 .../esri/core/geometry/OperatorBoundary.java | 0 .../core/geometry/OperatorBoundaryLocal.java | 0 .../geometry/OperatorBoundaryLocalCursor.java | 0 .../esri/core/geometry/OperatorBuffer.java | 0 .../core/geometry/OperatorBufferCursor.java | 0 .../core/geometry/OperatorBufferLocal.java | 0 .../com/esri/core/geometry/OperatorClip.java | 0 .../core/geometry/OperatorClipCursor.java | 0 .../esri/core/geometry/OperatorClipLocal.java | 0 .../esri/core/geometry/OperatorContains.java | 0 .../core/geometry/OperatorContainsLocal.java | 0 .../core/geometry/OperatorConvexHull.java | 0 .../geometry/OperatorConvexHullCursor.java | 0 .../geometry/OperatorConvexHullLocal.java | 0 .../esri/core/geometry/OperatorCrosses.java | 0 .../core/geometry/OperatorCrossesLocal.java | 0 .../com/esri/core/geometry/OperatorCut.java | 0 .../esri/core/geometry/OperatorCutCursor.java | 0 .../esri/core/geometry/OperatorCutLocal.java | 0 .../geometry/OperatorDensifyByLength.java | 0 .../OperatorDensifyByLengthCursor.java | 0 .../OperatorDensifyByLengthLocal.java | 0 .../core/geometry/OperatorDifference.java | 0 .../geometry/OperatorDifferenceCursor.java | 0 .../geometry/OperatorDifferenceLocal.java | 0 .../esri/core/geometry/OperatorDisjoint.java | 0 .../core/geometry/OperatorDisjointLocal.java | 0 .../esri/core/geometry/OperatorDistance.java | 0 .../core/geometry/OperatorDistanceLocal.java | 0 .../esri/core/geometry/OperatorEquals.java | 0 .../core/geometry/OperatorEqualsLocal.java | 0 .../geometry/OperatorExportToESRIShape.java | 0 .../OperatorExportToESRIShapeCursor.java | 0 .../OperatorExportToESRIShapeLocal.java | 0 .../geometry/OperatorExportToGeoJson.java | 0 .../OperatorExportToGeoJsonCursor.java | 0 .../OperatorExportToGeoJsonLocal.java | 0 .../core/geometry/OperatorExportToJson.java | 0 .../geometry/OperatorExportToJsonCursor.java | 0 .../geometry/OperatorExportToJsonLocal.java | 0 .../core/geometry/OperatorExportToWkb.java | 0 .../geometry/OperatorExportToWkbLocal.java | 0 .../core/geometry/OperatorExportToWkt.java | 0 .../geometry/OperatorExportToWktLocal.java | 0 .../esri/core/geometry/OperatorFactory.java | 0 .../core/geometry/OperatorFactoryLocal.java | 0 .../core/geometry/OperatorGeneralize.java | 0 .../geometry/OperatorGeneralizeCursor.java | 0 .../geometry/OperatorGeneralizeLocal.java | 0 .../core/geometry/OperatorGeodeticArea.java | 0 .../geometry/OperatorGeodeticAreaLocal.java | 0 .../core/geometry/OperatorGeodeticLength.java | 0 .../geometry/OperatorGeodeticLengthLocal.java | 0 .../geometry/OperatorImportFromESRIShape.java | 0 .../OperatorImportFromESRIShapeCursor.java | 0 .../OperatorImportFromESRIShapeLocal.java | 0 .../geometry/OperatorImportFromGeoJson.java | 0 .../OperatorImportFromGeoJsonLocal.java | 0 .../core/geometry/OperatorImportFromJson.java | 0 .../OperatorImportFromJsonCursor.java | 0 .../geometry/OperatorImportFromJsonLocal.java | 0 .../core/geometry/OperatorImportFromWkb.java | 0 .../geometry/OperatorImportFromWkbLocal.java | 0 .../core/geometry/OperatorImportFromWkt.java | 0 .../geometry/OperatorImportFromWktLocal.java | 0 .../OperatorInternalRelationUtils.java | 0 .../core/geometry/OperatorIntersection.java | 0 .../geometry/OperatorIntersectionCursor.java | 0 .../geometry/OperatorIntersectionLocal.java | 0 .../core/geometry/OperatorIntersects.java | 0 .../geometry/OperatorIntersectsLocal.java | 0 .../esri/core/geometry/OperatorOffset.java | 0 .../core/geometry/OperatorOffsetCursor.java | 0 .../core/geometry/OperatorOffsetLocal.java | 0 .../esri/core/geometry/OperatorOverlaps.java | 0 .../core/geometry/OperatorOverlapsLocal.java | 0 .../esri/core/geometry/OperatorProject.java | 0 .../core/geometry/OperatorProjectLocal.java | 0 .../core/geometry/OperatorProximity2D.java | 0 .../geometry/OperatorProximity2DLocal.java | 0 .../esri/core/geometry/OperatorRelate.java | 0 .../core/geometry/OperatorRelateLocal.java | 0 .../core/geometry/OperatorSimpleRelation.java | 0 .../esri/core/geometry/OperatorSimplify.java | 0 .../core/geometry/OperatorSimplifyCursor.java | 0 .../geometry/OperatorSimplifyCursorOGC.java | 0 .../core/geometry/OperatorSimplifyLocal.java | 0 .../geometry/OperatorSimplifyLocalHelper.java | 0 .../geometry/OperatorSimplifyLocalOGC.java | 0 .../core/geometry/OperatorSimplifyOGC.java | 0 .../geometry/OperatorSymmetricDifference.java | 0 .../OperatorSymmetricDifferenceCursor.java | 0 .../OperatorSymmetricDifferenceLocal.java | 0 .../esri/core/geometry/OperatorTouches.java | 0 .../core/geometry/OperatorTouchesLocal.java | 0 .../com/esri/core/geometry/OperatorUnion.java | 0 .../core/geometry/OperatorUnionCursor.java | 0 .../core/geometry/OperatorUnionLocal.java | 0 .../esri/core/geometry/OperatorWithin.java | 0 .../core/geometry/OperatorWithinLocal.java | 0 .../com/esri/core/geometry/PathFlags.java | 0 .../com/esri/core/geometry/PeDouble.java | 0 .../geometry/PlaneSweepCrackerHelper.java | 0 .../java}/com/esri/core/geometry/Point.java | 0 .../java}/com/esri/core/geometry/Point2D.java | 0 .../java}/com/esri/core/geometry/Point3D.java | 0 .../core/geometry/PointInPolygonHelper.java | 0 .../java}/com/esri/core/geometry/Polygon.java | 0 .../com/esri/core/geometry/PolygonUtils.java | 0 .../com/esri/core/geometry/Polyline.java | 0 .../com/esri/core/geometry/PolylinePath.java | 0 .../esri/core/geometry/ProgressTracker.java | 0 .../geometry/ProjectionTransformation.java | 0 .../esri/core/geometry/Proximity2DResult.java | 0 .../geometry/Proximity2DResultComparator.java | 0 .../com/esri/core/geometry/QuadTree.java | 0 .../com/esri/core/geometry/QuadTreeImpl.java | 0 .../core/geometry/RasterizedGeometry2D.java | 0 .../geometry/RasterizedGeometry2DImpl.java | 0 .../core/geometry/RelationalOperations.java | 0 .../geometry/RelationalOperationsMatrix.java | 0 .../core/geometry/RingOrientationFixer.java | 0 .../java}/com/esri/core/geometry/Segment.java | 0 .../com/esri/core/geometry/SegmentBuffer.java | 0 .../com/esri/core/geometry/SegmentFlags.java | 0 .../core/geometry/SegmentIntersector.java | 0 .../esri/core/geometry/SegmentIterator.java | 0 .../core/geometry/SegmentIteratorImpl.java | 0 .../esri/core/geometry/ShapeExportFlags.java | 0 .../esri/core/geometry/ShapeImportFlags.java | 0 .../esri/core/geometry/ShapeModifiers.java | 0 .../com/esri/core/geometry/ShapeType.java | 0 .../core/geometry/SimpleByteBufferCursor.java | 0 .../core/geometry/SimpleGeometryCursor.java | 0 .../esri/core/geometry/SimpleJsonCursor.java | 0 .../core/geometry/SimpleJsonParserCursor.java | 0 .../geometry/SimpleMapGeometryCursor.java | 0 .../esri/core/geometry/SimpleRasterizer.java | 0 .../com/esri/core/geometry/Simplificator.java | 0 .../esri/core/geometry/SpatialReference.java | 0 .../core/geometry/SpatialReferenceImpl.java | 0 .../geometry/SpatialReferenceSerializer.java | 0 .../geometry/StridedIndexTypeCollection.java | 0 .../com/esri/core/geometry/StringUtils.java | 0 .../esri/core/geometry/SweepComparator.java | 0 .../core/geometry/SweepMonkierComparator.java | 0 .../com/esri/core/geometry/TopoGraph.java | 0 .../core/geometry/TopologicalOperations.java | 0 .../esri/core/geometry/Transformation2D.java | 0 .../esri/core/geometry/Transformation3D.java | 0 .../java}/com/esri/core/geometry/Treap.java | 0 .../core/geometry/UserCancelException.java | 0 .../esri/core/geometry/VertexDescription.java | 0 .../VertexDescriptionDesignerImpl.java | 0 .../core/geometry/VertexDescriptionHash.java | 0 .../com/esri/core/geometry/WkbByteOrder.java | 0 .../esri/core/geometry/WkbExportFlags.java | 0 .../esri/core/geometry/WkbGeometryType.java | 0 .../esri/core/geometry/WkbImportFlags.java | 0 .../java}/com/esri/core/geometry/Wkid.java | 0 .../java}/com/esri/core/geometry/Wkt.java | 0 .../esri/core/geometry/WktExportFlags.java | 0 .../esri/core/geometry/WktImportFlags.java | 0 .../com/esri/core/geometry/WktParser.java | 0 .../ogc/OGCConcreteGeometryCollection.java | 0 .../com/esri/core/geometry/ogc/OGCCurve.java | 0 .../esri/core/geometry/ogc/OGCGeometry.java | 0 .../geometry/ogc/OGCGeometryCollection.java | 0 .../esri/core/geometry/ogc/OGCLineString.java | 0 .../esri/core/geometry/ogc/OGCLinearRing.java | 0 .../esri/core/geometry/ogc/OGCMultiCurve.java | 0 .../core/geometry/ogc/OGCMultiLineString.java | 0 .../esri/core/geometry/ogc/OGCMultiPoint.java | 0 .../core/geometry/ogc/OGCMultiPolygon.java | 0 .../core/geometry/ogc/OGCMultiSurface.java | 0 .../com/esri/core/geometry/ogc/OGCPoint.java | 0 .../esri/core/geometry/ogc/OGCPolygon.java | 0 .../esri/core/geometry/ogc/OGCSurface.java | 0 .../core/geometry/gcs_id_to_tolerance.txt | 0 .../com/esri/core/geometry/gcs_tolerances.txt | 0 .../geometry/intermediate_to_old_wkid.txt | 0 .../esri/core/geometry/new_to_old_wkid.txt | 0 .../core/geometry/pcs_id_to_tolerance.txt | 0 .../com/esri/core/geometry/pcs_tolerances.txt | 0 .../com/esri/core/geometry/GeometryUtils.java | 0 .../geometry/RandomCoordinateGenerator.java | 0 .../esri/core/geometry/TestAttributes.java | 0 .../com/esri/core/geometry/TestBuffer.java | 0 .../com/esri/core/geometry/TestClip.java | 0 .../esri/core/geometry/TestCommonMethods.java | 0 .../com/esri/core/geometry/TestContains.java | 0 .../esri/core/geometry/TestConvexHull.java | 0 .../java}/com/esri/core/geometry/TestCut.java | 0 .../esri/core/geometry/TestDifference.java | 0 .../com/esri/core/geometry/TestDistance.java | 0 .../com/esri/core/geometry/TestEditShape.java | 0 .../geometry/TestEnvelope2DIntersector.java | 0 .../com/esri/core/geometry/TestEquals.java | 0 .../com/esri/core/geometry/TestFailed.java | 0 .../esri/core/geometry/TestGeneralize.java | 0 .../com/esri/core/geometry/TestGeodetic.java | 0 .../esri/core/geometry/TestGeomToGeoJson.java | 0 ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 0 .../esri/core/geometry/TestImportExport.java | 0 .../geometry/TestInterpolateAttributes.java | 0 .../esri/core/geometry/TestIntersect2.java | 0 .../esri/core/geometry/TestIntersection.java | 0 .../esri/core/geometry/TestIntervalTree.java | 0 .../esri/core/geometry/TestJSonGeometry.java | 0 .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 0 .../esri/core/geometry/TestJsonParser.java | 0 .../com/esri/core/geometry/TestMathUtils.java | 0 .../esri/core/geometry/TestMultiPoint.java | 0 .../java}/com/esri/core/geometry/TestOGC.java | 0 .../com/esri/core/geometry/TestOffset.java | 0 .../com/esri/core/geometry/TestPoint.java | 0 .../com/esri/core/geometry/TestPolygon.java | 0 .../esri/core/geometry/TestPolygonUtils.java | 0 .../esri/core/geometry/TestProximity2D.java | 0 .../com/esri/core/geometry/TestQuadTree.java | 0 .../geometry/TestRasterizedGeometry2D.java | 0 .../com/esri/core/geometry/TestRelation.java | 0 .../esri/core/geometry/TestSerialization.java | 0 .../core/geometry/TestShapePreserving.java | 0 .../com/esri/core/geometry/TestSimplify.java | 0 .../com/esri/core/geometry/TestTouch.java | 0 .../com/esri/core/geometry/TestTreap.java | 0 .../com/esri/core/geometry/TestUnion.java | 0 .../esri/core/geometry/TestWKBSupport.java | 0 .../geometry/TestWkbImportOnPostgresST.java | 0 .../com/esri/core/geometry/TestWkid.java | 0 .../com/esri/core/geometry/TestWktParser.java | 0 .../java}/com/esri/core/geometry/Utils.java | 0 .../esri/core/geometry/savedAngularUnit.txt | Bin .../com/esri/core/geometry/savedAreaUnit.txt | Bin .../com/esri/core/geometry/savedEnvelope.txt | Bin .../esri/core/geometry/savedEnvelope2D.txt | Bin .../esri/core/geometry/savedLinearUnit.txt | Bin .../esri/core/geometry/savedMultiPoint.txt | Bin .../com/esri/core/geometry/savedPoint.txt | Bin .../com/esri/core/geometry/savedPolygon.txt | Bin .../com/esri/core/geometry/savedPolyline.txt | Bin .../savedProjectionTransformation.txt | Bin .../core/geometry/savedSpatialReference.txt | Bin 321 files changed, 32 insertions(+), 30 deletions(-) delete mode 100644 DepFiles/unittest/jersey-client-1.5.jar delete mode 100644 DepFiles/unittest/jersey-core-1.5.jar rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamBase.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfDbl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfFloat.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt16.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt32.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt64.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt8.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Boundary.java (100%) rename src/{ => main/java}/com/esri/core/geometry/BucketSort.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Bufferer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ByteBufferCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ClassicSort.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Clipper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Clusterer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ConstructOffset.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ConvexHull.java (100%) rename src/{ => main/java}/com/esri/core/geometry/CrackAndCluster.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Cracker.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Cutter.java (100%) rename src/{ => main/java}/com/esri/core/geometry/DirtyFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ECoordinate.java (100%) rename src/{ => main/java}/com/esri/core/geometry/EditShape.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope1D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope2DIntersectorImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope3D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeoDist.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeoJsonExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeoJsonImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeodeticCurveType.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Geometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryAccelerators.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryCursorAppend.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryEngine.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryException.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometrySerializer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IndexHashTable.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IndexMultiDCList.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IndexMultiList.java (100%) rename src/{ => main/java}/com/esri/core/geometry/InternalUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Interop.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IntervalTreeImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JSONArrayEnumerator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JSONObjectEnumerator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JSONUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonParserCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonParserReader.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonReader.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonStringWriter.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonValueReader.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonWriter.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Line.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ListeningGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MapGeometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MapGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MapOGCStructure.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MathUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MgrsConversionMode.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPath.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPathImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPointImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiVertexGeometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiVertexGeometryImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/NonSimpleResult.java (100%) rename src/{ => main/java}/com/esri/core/geometry/NumberUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OGCStructure.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ObjectCacheTable.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Operator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBoundary.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBoundaryLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBoundaryLocalCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBuffer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBufferCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBufferLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorClip.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorClipCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorClipLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorContains.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorContainsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorConvexHull.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorConvexHullCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorConvexHullLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCrosses.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCrossesLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCut.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCutCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCutLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDensifyByLength.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDensifyByLengthCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDensifyByLengthLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDifference.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDifferenceCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDifferenceLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDisjoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDisjointLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDistance.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDistanceLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorEquals.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorEqualsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToESRIShape.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToGeoJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWkb.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWkbLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWkt.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWktLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorFactory.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorFactoryLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeneralize.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeneralizeCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeneralizeLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticArea.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticAreaLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticLength.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticLengthLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromESRIShape.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromGeoJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWkb.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWkbLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWkt.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWktLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorInternalRelationUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersectionCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersectionLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersects.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersectsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOffset.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOffsetCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOffsetLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOverlaps.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOverlapsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProject.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProjectLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProximity2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProximity2DLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorRelate.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorRelateLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimpleRelation.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplify.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyCursorOGC.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyLocalHelper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyLocalOGC.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyOGC.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSymmetricDifference.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorTouches.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorTouchesLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorUnion.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorUnionCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorUnionLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorWithin.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorWithinLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PathFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PeDouble.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PlaneSweepCrackerHelper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Point.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Point2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Point3D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PointInPolygonHelper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Polygon.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PolygonUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Polyline.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PolylinePath.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ProgressTracker.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ProjectionTransformation.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Proximity2DResult.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Proximity2DResultComparator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/QuadTree.java (100%) rename src/{ => main/java}/com/esri/core/geometry/QuadTreeImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RasterizedGeometry2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RasterizedGeometry2DImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RelationalOperations.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RelationalOperationsMatrix.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RingOrientationFixer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Segment.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentBuffer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentIntersector.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentIterator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentIteratorImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeModifiers.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeType.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleByteBufferCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleJsonParserCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleMapGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleRasterizer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Simplificator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SpatialReference.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SpatialReferenceImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SpatialReferenceSerializer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/StridedIndexTypeCollection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/StringUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SweepComparator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SweepMonkierComparator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/TopoGraph.java (100%) rename src/{ => main/java}/com/esri/core/geometry/TopologicalOperations.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Transformation2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Transformation3D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Treap.java (100%) rename src/{ => main/java}/com/esri/core/geometry/UserCancelException.java (100%) rename src/{ => main/java}/com/esri/core/geometry/VertexDescription.java (100%) rename src/{ => main/java}/com/esri/core/geometry/VertexDescriptionDesignerImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/VertexDescriptionHash.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbByteOrder.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbGeometryType.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Wkid.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Wkt.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WktExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WktImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WktParser.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCCurve.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCGeometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCGeometryCollection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCLineString.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCLinearRing.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiCurve.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiLineString.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiPoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiPolygon.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiSurface.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCPoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCPolygon.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCSurface.java (100%) rename src/{ => main/resources}/com/esri/core/geometry/gcs_id_to_tolerance.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/gcs_tolerances.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/intermediate_to_old_wkid.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/new_to_old_wkid.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/pcs_id_to_tolerance.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/pcs_tolerances.txt (100%) rename {unittest => src/test/java}/com/esri/core/geometry/GeometryUtils.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/RandomCoordinateGenerator.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestAttributes.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestBuffer.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestClip.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestCommonMethods.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestContains.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestConvexHull.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestCut.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestDifference.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestDistance.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestEditShape.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestEnvelope2DIntersector.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestEquals.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestFailed.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeneralize.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeodetic.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeomToGeoJson.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestImportExport.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestInterpolateAttributes.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestIntersect2.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestIntersection.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestIntervalTree.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestJSonGeometry.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestJsonParser.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestMathUtils.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestMultiPoint.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestOGC.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestOffset.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestPoint.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestPolygon.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestPolygonUtils.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestProximity2D.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestQuadTree.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestRasterizedGeometry2D.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestRelation.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestSerialization.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestShapePreserving.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestSimplify.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestTouch.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestTreap.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestUnion.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWKBSupport.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWkbImportOnPostgresST.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWkid.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWktParser.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/Utils.java (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedAngularUnit.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedAreaUnit.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedEnvelope.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedEnvelope2D.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedLinearUnit.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedMultiPoint.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedPoint.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedPolygon.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedPolyline.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedProjectionTransformation.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedSpatialReference.txt (100%) diff --git a/.gitignore b/.gitignore index 82b5005e..03c080fb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ esri-geometry-api.jar .classpath description.jardesc *.bak +*.properties # Intellij project files *.iml diff --git a/DepFiles/unittest/jersey-client-1.5.jar b/DepFiles/unittest/jersey-client-1.5.jar deleted file mode 100644 index 62f790fa6fba8d2783a059b4a493fb28bb563373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128096 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 sc^g^m7M-Jkk0+RkQFO>YU`8qwzbXzaVr6 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 z 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 z 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�Ua>bx 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

0?OHVz9VA0VGpf&kpun1 z&NVK{;k+^+fX!*XWP=L&>AA2c7u)@~q6hJ`yyv598N6mc2fHg@cWWHjIw$nnbAraRZd0~PGr*2YJTOB~s??}wjT9(>TTE_HG3ug>|f+%!O0pH%_vQx`5o zZ<$*W>~F&T8FtTpgYB2+OaPC)2Q=t0h{h8@PCR#}ZqKs0|E7U8$@KRE-N7t_kCZWh zfuNThgGDi=`nXXrv9Yp|#VW_bDa<^-wzhex$=VttmF5aXj;03z;hS5Kz3u1NL!O6wAcEstX19+nTtZX59HYR_wMW*4(=6$ zKL3<2k+i>@bGWm=)tq0NSe;p1nU!dIZs_RJr5JEoaMBheSe}~17^E~b398+{hjmgG z$g~Xx+BXlY6?}ZcYdxV%W7U99bf7PqycJwz5`1)FRp~-p@i+j1)ve=Az)UJnD8jiE z5_W^PskUq3z03R%r?xbzKhqjk&ixYQV0=+EJh+mKUVBf~AFZIsNTZd9zss#=?=3eS z`hH_+`hpKEup?A*>_ZQgyGwvp8LIStatdnkC#6O7kO9~L z5@D0;gcQaDy1p{tOiAPk?}}Kyub=IvG5%N+?3ND}w>GjvkzpxXd{{HGub)d?HlfOE z;pxvOCiS6l`z`PV?VDK@A^&I&7TEI3n@{WWq|Q}sZ-!5dodX4x|=Y#g~1TIC3al#ti|ceikTszEa-jVkfyOBPOH5fHa-={XL{L}GFf850>CilKP)=*ZRGM# z#uH|=HwiY>?^Aq?6yDhE6z??zi#RvtrSqk7m5cX3-xA=HgYZ$&NZMrjh7?jPI@Sd@ zbxy-4hT@M~+zTZR0!1S`UHNpjyWtFsodnLPReDCo*;>jt33~l0D{{2FEiC%4c+iB^ zR%`;;42%=jgPBHCQAA3Hx!LvlO!~*Q`b1EnBEyVsgiOL?9gw3R;Z z1CSyJN|%eA^dyg3Btm8jQBiQcf)0y=g9U!s6e@{hFo4#Wis)}JtWBxpUb4YZv7%^b zre+?+@kfUK_xg+OD$7#A{uf7s=>*PP*%~6Irx#p z5*`K`Jnfg_B?vDY?m}}o{Rk=7nZx|ijn!Lo@&>WuTwIm%Rb7)FDSp%f;7ghYESdx& zkXq8@YnLywjizIo_2QahtjWx`K>J&#!>KFx7fNeo0Yx;A@QifNR>Gz?X$R;d(T~Nn zW>sQ26b<}kbmpw7g{}`p6g7k2jK9{4bJkpPBhMzZod~i+&oprYuta2nZQ5qz*$58j zHONTGg^}iLQ)MX*8Ab>TG>EFZFdEmH@1m+joS=rsZb`DH4nx{=h6*ERl1XM-&Ei&f z6bckC??kSw%Hm#?I!Ui(%fj5}Mg>9JalXl)vko>ReT6G1ZbbiJ>V^{F`24L1Sm5Zu-l>$*aH=wZS<(>l*z>Z&V<5Sb3QSJej0OfD=RR#+|9< zH>@X{qJh*zeLs=*dV@=DdkXRR5jq!=0DcDg^KlYj~HNkJw#~mls5t zWcsvNP8`EB(oaT-Wt&T8eR!q~S1mL!k7H3zR8eA{iz(ViPeO9Zio{7fjP4y>ksM?Pl}HVMtTKu=G{=gr?4*s*X zwWrIs+q+#$`}@L4-Q>v`>5mt2{e}!h`zIg$hBpv%1(fNg)(YFFfFaWiwhc-6Gi279 zRE*C--3M4%7zb7>qd~Ew%f1KDPR_*a=A_!AAiHNh?BVlO5^O`wDjNOPLG(=ORww}n z(dnZJ&>PalRs91;{oq_Y^4A12L$Y#rlEYvIJ1yjLGhm$jv5`z&up?}$sZ_|NTi?b5 znu8A@bGz&2<3oxzI2+BQK^Q6}ZYyZc){KrdbyvA=X9WkCy#7DR-ZHq&Ht7;}?3kIE znVFfHnNenDcFZv|GsTXXQFdZxW@cuH89qPH%nWxQZOKngJb8;m9YPb|2+o2{#ryRoes zz)arQ@gG>$O{x1YJxjJ^f(n}W8;FApqy3IDPORQHN2+5#JI?XMAMxi5&vpc_Ac7-T zkrBu+%RnG!5@$oh%iHY}_!hW_fW0rA?;1)_OhX;Rtq5{%SVPL1jPei*W>iax%z4>x z2y}%>PTCAlZ@E!xc5Lc!?t@t>a(i1nsvV82C!aAk2lc`n1Mw=78k?V8H5p~=bsKQv zL}YBA!elmJOiq(yhj+jis?+mauAHLX|K)8BUBdyetv1kes3YcCwi@nP6Y^86okIq% z8W~~`8tN?=3LmtffB$;Z$4&$nGnyw_=L*{+&hgs@Vb|VsPW9C0!zun>Cr+&z&BD9S zApLj2%6~i!C;&V}9sY-AMf21_PZRkgXT8Jcd_3m-ah}I1E3tUk+s*A=+iKBFkeBh zRo=lvp2pieYCINCL-IY{EG!Pj#xkCAGF%ofUk@=IKpxR@qYB^+Z6#=Pk#5L|4KdcO zh%G{vM9f2rL&u<T5MXOXR*h-dgac?_gy7JpSJd3(lxl!TPrlHIs=k)Te+cLjZ=r`50`$imk zaBTa-s3VG8A`2+~u#NNLTDM0=nIl`&zw=bd>iVsF+?6@c17-}UpGD!~PMnnOF1sue zHaZRLWJ}y0n66J+z%Elo3u_3i^bs;v^zEGWmfwsHV%Etn@;LQu-jZWDu*jK#_Te}j zYVL2$$*PmpPVg()p0I${xQwYURqvjd<#JY55Z%7}NFMtrmCl(Fr(3$<8_lCVIJXtv z91@N%@?^!zqX@y4jnM9o zxq<(4XVH{0tD25<-Z>|?Qm8y!!b}a2tt7`yb^dp#GVd&TxSk(t^uQv(an|zBrAV_k zt>K!`SN>L5k6?Bsm~8^v6y|)O?x@eBVvI`F)4tLR-+4j(je^`-D$C5v9hW%vdJu^IL7~oXD9#{93^X%Q_tdbPAM$mMd!sZ*w z988~AK41rzTwFds$Di|p>wN@e7Bx3!yu7w0`<&@ z%NIx|q(D7%Vhpi#n5#$Xs{OCwD73Ant#+2H!2D;7-fJ6d4}&md0oCamr-57VcQd=y zly)nhn7cGzMc_n+PjK2|P5BSIAw}YztfzGUq@hM~qski^m<-PlkP7vm!nefzA3oM% z-dB`~0ReoI=`*g2Uie}?nouT)j3K$0;zt&7@=#-+G;E)S@#ajv%{WW*`_zL~>Dq;M zqYs9TDqvc??G)VbD1T}@!^897^NS7Xw%LZGJGw@STB3R|-#!u3&XU5P^-!t^zqVi@ zz(rvPaF$A;sIM3x_xN-FyetK6UX9=Bc4Y``rMwa|?ru&WJOC#IMIZ=CqXf6OUy(Nl z^R7%^u?4#gkDfHW-(xBQ4o?KH5nrS7PSqKgPP^9(4=Z-b0@EH@=R)tM-HO<)-*7wj ze4A-)Iuhdm*M$MeHK;X%7xR*nb?@BGZuxH5oi#?c>g(TaV%no5;!Y3v#SJo31a-ae z&!ag${&o@fc>C^;^{KioNBW;&<^QRT^Vd|hD!{@HVDI`5q!OwyYTcuR#J@{xjUM8D zT#dW#*=^Sk5Vlkr!i+w4jOnA|FCZMtli z)3rlT=FI1-2<)Fmo)8Y6Zx;;SBarM-U4`BF@W1|+BbT5KT@8Q25Apx1m;H}&WEFsu z8^GnCL8($1l>z%)a5-IPYSZ+`&W(~mM+tK686;0jDOuxdnv*xS>Zi#tHsL>}q5B%~ ztF+L;8-}DW_z%}`o%0L{@<1w$y9XQNu)}P{?$giI*NoC>G-ESgQy~*8sLFKd)fy+5 ztff>eC)+Uew8QqD(^9mEgGr6gyfxhk*D{2%h@#oSsaJD!->(GoR8dh!?lY zvoYiOH?KTySe+iS%({*OdgybkVNBR(Z!2)Qm&Lp9ze=sJUTpmmXT0 zZwBjK!<5M36RqPEqfC?M@nkig54?r7st=7qyV!2^+v?a%GM0v{ExIQ(a`mnl|Xt=p%1qJhwi`%E*~Md%wNB3;=z^I_2K0i|@7br4Nsc<>fZ3jUUur8z&Ag z$HUmKE#A$IeSuqP+~qb2E2v>owirr&qp-j#=OHkR-q*>F+-R&mCg=d^Xj*W)r}+a|K+{a}6B)6_z8#Jm#hLaGhpY`me`^q$+E3HlT)yIpxd`Sy{w%~;Moem$ItOPq!I}HfE8I;&d zW;t>*0?dQp6d9R9;?;+*=tfPwiAtS6Ri~ZwBSaa9Eh444A9x+Jg z`rWB%t4DQHd2t&H<;yb>Y697PEw)0-z8FNo?w zs3{mnS?z`~o66Sj!Md|@n6L)!S+4DjtHsp*WckL9{svzjX=z>J_hod)&*xX8`a*gB zZFZIx84*SE%*eMQpH?E&_^xT%Y(jLyXzxhy;4J@1}6%+!a zrL0_%eTOL+tjQ(UeKSpci5hh0MqAp86tDH+N~cKH#UEd=y!@fl)W3quk!h)E^@PLJ z7$thj^*)c*3i~lqNm7jUl~%!)=9zo19UY zNDO`BUKeiW&GwGV-Y5(R(-h-fHpgKZKxFwF0qQN>yS7GXL((a}jLo7-;5Xa<9)Qw1_0Z5&N+wDZBA^WLQ`(&NXHKF!yAZ$s0bL z>ltuSpN+&()}L=yJ5PU(!ab!a;OlJ4+*ET5du))MgFnM|5BA_EjM0~%1e7z%g;fLh z(?cZytFD~Uc3JNU;ypXEVf$r6X|?bclm(0Wqs!6;=CUMD*2}dmaEm(`op*_*YH}C6 zNtx!-+Og_}##oQC`+BxfbAL23#&PDV@WUh9l7}((5y;h>`JHY;G~94f=0bH1kPo%& zS^=y!*XF#jV3?kfOcal9*>I-tG`(lwvti7^wpcFd4|0NGJy-|kAz8}y1FhJDNTP?w z7XUuv^hZPLb_qboiPEUw^isrA)M|%$n}R8Q&YH5*Y|c zA7a6glfDIInd#>E=1V``zT+0Fh~49ha0@BL$CF8Q5E=g>`W*6v_|tcy`B8ULL` z%E4ocSJ^PB+DF>DP@nNB>u5?gkfQxAaZN1FKqd2N9`X16q;1Nijax!j8J@!~jd6$u zm}fMtD;7!>#BA_C0Fk{R2y6dmch>V$^z(nezf$}^{67Bq!%(+zRnz>Ovd$SE%NdqW z_v!5wvn)lDfugsv61D|pkv2CP0j5hD^_aG1v;`I<@orDZt*F^l1?e>DJgZpOA&NI@ zgl=oEkmwN7&sq2}rhPQp95XID)4y$7uOE(;gt5^vX}fe?`W{~R-ESPaZJ#$Bf>iz{ z(<>OpGn4JEX8TJHm}775-<3tvv&1^-q$Q*{ zWzV9rgaNYm?vkUcSQK2mB?dUK8J)c~2Hde3ou2EX@mUPE@3Nz_qc5Gj4F(jjHxKV1 zSzNJeoS#LpNm=~+j^hA(fY*@OMKd%r;KVUFCqw%+L@=%Td_@yXFbao!FAmjlN~E&W zZYdku%wS}RGz4{5g0Zn#oIt_mtcyp zr)U95^{DLU)W-+axoYz?nH^eWkl!n%rrAuI>X3V7PD#mC)_PQZla19EeP-Ld5jufz`#=MnnJN+ws~5M- zXRN@A8cU1{o1ZwWo`~TV=}zBmD>|Z*vsy^ZEyb$YB6FAQj?qt=r_P=_3ZGlkV;P`1 z{RccuoYJH@F#MQXPGL6qJEBiYis~v)KeS6Q>gD7khNqj7n|tY0lG4CASMPc3EQaQt zgJ(+sHd9=Wc{4dpl>pA}T^t zSuPAbR|_n!Y*hI7`aRrajIh%>ghcvo>oWK?Wb4wlMs04Bg9{Eau04_me^@J^pY_+E z&0YNf1uKGaH*N_sG>r=)aq#cW+PpdN06y_My#LO6TPihzqiD%ds)d&JFPiT9i31K! zy^#X;1Y~A-w%T|Y7Gk@=zE`Cd zd#9XM^KII>fJK(Z`WaG$GDcAeNgl)C%fuf* z(tx}3MruCSh3OirqpP>0Puq*efxN*yESssnHaf zP?kBXt~-<&BD948#HOX)3jzmahSD?yKeC^D~&pj`<5U`m^!AFf=~ zQ7nHjP;LYr@XyeSZyoVU2zt4i7z8K)$1k0=XP26Zg7(vE5Ec*EY&qe#W$9++!lA7* zk8tX|Q0*{f`wZ`3keN`F?P=qJ@R|=nNsb;Zyow?h0$*Bcl)MeZ#P)-)x__M)Z#q9$ z{V)`KBUyLzROs>fO8cE1c0pVY`HaVeK4_d`fhIj|iO%Fxhe}+iuP`ml1Cb_1P`s;D zf_jvsKth%yBEl16d<+LO6U9{0P$tb7RrzWro3LxIY&~&NV8YobJu(;J6XEgu4dHoe zZyh@aOXE^nHf3Q})2G>T|V9X>l$KmFl_<-o6FR2=} z0`>LW$2&^4w!BN3r~Ir!(K(knEFf03|8zyKU<`HSB~wjrY9(}rIQgMN4OlRaIRaXs zcc`&EzVb)p8g?H-8^FIJQb30yQ#x5vuiMx!(ylBe>uctbwLo56B64z{u-&`DLcC5^ zoaIN5fA=MH}LpRih1vvuc60Dv8&4LQCN<@Dq}%2)z?Fj2c0( z_JI1SDdpFtk;4D83VB9{*l&na7NBT#8iIzT@@ulRzO?BJF+2?Qk*fN=B0X90ACZ-< zS+#pcdb(m>Kf9XvqcT%>n$t>C1DaJeNlBX1a#LZN)9T*5kr6jK8Gs+zx|Gn03Mh&L z$<^GnsL?;rW7`e1 z2u=8uI3^?hH;_Z~{{V9S5<=Odi;6@{E#Cl>iI!oT#LOAi+k#n$&Bjz0xzqw z^&QE61gJaVW_5cy^EBMnmUrArlx<&6oc-p)BXX%YLyBZgrP)yoO^|loYJ5nrtvT~5jVPBXFi@s#JO0RV) z%yi1H`NhnxJL$q5bMwE=otwU3HXh!ozBKXk^Os5oIG8g@;e25^bll8(xOiLtv;J^b z%DMRn|84O4l!>5!pUK@+IMIUqNeBz~L_ej$LiDH`_U2Yx+91&;FJZ4C(O2QZ-yo53Xp(VcIgRY8-t0Y6 z&qq5^b8m%Rx{GFbac^Z2-B+}m6Wv!fpWMRcUQb&{KK+M%f22-ZWg5D#F439&KBl=F z_$G2IZAF@ethu%ziv`-CTU1Xt+-xl*c&7I~9PC%q5~mzr>E5|n7gp2ul*NHay<@3V z7#%0A2DqnldWduBVDC#s1*_YY{i71+IT}e=a@n}*BW^0B%&GHME8P2_-~9YOcpH|= ztdxZrb<~|kK6JDSQ?VgJA+*%%3j^%Bs7w11>Sa>n=ys+Cv=hg>)OH$$d(qHIL>Tn4 zwscOGG34B87mL-Q}X8@7T>gnnwza zQoKT^^#LQqA4AQ|$DvjSJ;;6E7;)&Z4QJ?L_Boy&H47fW1)H7RN`YeoC%9^z zE~Y|(o%|64O5lp}`E2w|Tbj?OPy*AcS=j?fI*jFNT{{P@OIS?fdB~`l_qY==2-@I0XjhTK%w> zRJ1^ar*WXa0a=w8Mq5&e=ykVFH6fo}D6v9rFe{bW9S|2ik$c30Zs|R2k`P@rvqA0R z)(5YondTiave8~-y)On{uh{vSH=9{4h@=8cnbT~=?i|XLrY!7Q0`M<>UX;L>-zJXS z&tI;>_%-mg{+Qz!+)P*B*0Y+RIU0mJHd!5cH-Ii6iEOFxU;>>R-cao2W231R0mJ7}@rfpv>NpRy50;2W^Bl~ws zZ$u>%ueW+%3$Cd4HaWiTBZvJqeT{TR!^`q<5C%?2e-m{5hGF5vc%M+t0Jah60V+(4 z2xP4X=OT^c=SJYJ4@d4H%607SC$$Xfacl5E4kTwIf8l~=DD(<39c4l5-d&=JKQWY$ z+QNx-Lcw^XC$So4u7zX&UDFKKJ+Z~otfc8;(J<(fm!0q=B)chOF-mfQ)AqiwS@X-S z;u3-#0-w>nFvT_9_3OE3=0xOKNqW85kc~Y{R696*kLo_X@F=#C7NZnDU zTQ&OwlJ{LUKi<~aKtSP2SENGLw`A0@DUJ)F&1zW$?3;8C4pc+uxy#M*6)Nc^*TzW( ziL&CNnHMxZXL-G?rWyCBXE!pzHM`Xd$wf8$F*l8WE`|P2mg8fZq@^zYxwl;Df~-~x z=!e`h{ZKgG9nFknO#7@Kj`Bgjzi?~XtZ-23j5q!0lmjy$K4^lOonW1x|G~dokV_et zd`4tlzA1cusoUpA`c|=opA2q5-l=f5Sew$4-FN9s&hN|J@lAOL!YXDC&5^Cs!B5_y zV!qF%mzREw*Oqotj$eMzhRy2K=A?4|j(28Lb(XN~IL?hNra-C0YK^gO)erT>g5s93 zD`MU&RxiFaZK*k{HIJzAA<)c_eaaBIdvn38DdypWZ16iIb%XWe@+bLR9Wa#|@Vvsl z4|$W%IP{C^@sfvVwn_aZUbRV|U7Z$7AKA#qro3#0#Js%RJyO05;iR{z>|E@e=ww~J zY_LK})<0rNn-STBlEh>YA|m}y@R~a24YPNZL-mgX*0f@f{=}`MT-5M(5cmFvqLG;r{QiQWq>+Ks_Z8ES*#YxURjY6 z&W1eO^`_1U`=3JINvb{V+dc*L{BAZKE(xMJN*vzihu&NqJi*3v<{yFQWOVcfa8Ya%>6TS=`5JqBFJ+!5TNo zq2Z~>JvnL4B8ixo6&H5&+{DOtHXO4(Is0@J0S9%#LQKI5UUADP|gCsz- z$`I281(QXbFDND!CTis9g`%6QquW3Gk@^Z9FrS-(v**)qWAbBijEZ6)n>8?Cg5pJ{ zp7{{kkdhqR$~MinAs>}@#FhK(QZsS}HwYJTzfrHixrIR#ICHGfhy*ku^h-PDToJWc z*>f&XIc3MRggoz%s9U?CSis4Lr515W+ZXGc40-uDRK+4XoEA%wz(u-kA4X?IkM>c$ zmy)^~|_jM3SKj;}gNo?L`mw9eg%I zP{;qY#QOR-igeYD>-@xskpC4$68|R@sqSp`FP*&(79&oj%B#vnQ{Ae#Dcu!UBcbPd)WOW?&tMi!@JiL=F%J;LJHjB z89AG4FCB4h9uyz14}*pv_651WkvLGOOjO}?q2Ww);o?vp3F65460X?3Kns#hMG^K- zD4KXugdLhFktLIr!SOd_7I3@3)tOmm5qBA-a`ze;us)XS(mL6wQXhvz@5!rAx1h_S z+7L$bs*jtuCU2_yYuq>ZxG&TACVTbt#U+%-LD#N8D4R^+pBr0i%)$T}{@cL`l+b0-h$o9P-kjWx&FyOz&W)r^*DKGMomJ|0@_ zf54^y^{%L{uF1&UcB~lYCuKn|_z|xc7FbwR#t0*`KAP(x1qd@hKFZutqt0=2NCdC% zwwrAd>$3mqr$2?hdU0f3Ge1WVOT`-9oVQ=`eC?Hs2s`=XP^zn@OdYP@i5{g5|5{?m z)wxV}UOu?d$F^z2rZ5|au%Ab>3141cl`eeE-Om2gFd|7sncYs(tPc$rby$xQ_6?)v zG;#zt14uZ&+(G!*NfgoV;abhJ4C+yC-d0p?b}JKuyZ2JHH|xR4za^=vB$Yc-{p-L_ zDKioO~I4Nu`GZ0AolX7RH22gMUL zMmT}`sJB*4t`*Io_Qr3nj2OVSCBJV+N@I>jhDOI$w`6g55*`RQX$|>oU9cDkS+x4z z>-RV|k;l)v87SX*RqRCCa)z4C89}-Hh-TJHPUs22Vqxh-#l#wzDEku0g@>DC!bAWMF{sJqB|5u`6la5ni(5TAZQKg(~R{{i1Ora(GJDDJYVQ zbAIOz9j>zUi!zL2=W(zD7&g7*ss;_8@7_2rpC70)$a1C5L2j$Kh05wR?OhLo@M{by zgUlDT{Kk=)G0O{uU*JBXg!){Hrg4xH+kQCvVIUIMfh~pQ*D05eBYu)cJYr|U&u&RX z!mPDoi0{rxVkBpzam9*g5LI7?Va7!?1Vs4qqzMaOlz;TQATZ-B4jsDftX;KA;NGaU zrCG(!UP5=vNFG-mfnGgQ`o3>Mq2ieyliuwG3_5Nr-U;Lih;82KlD$vh6?`-i8Z)+G z-Hyf1z<5fSMob-wl_CA`KA@EPfe6+bRFO++`gyR(E1-6c*fyQ)tVV~ONZ=i|e{dAlM z$w#{;I+lLpimwaXqOcR%Y)1zAS!~EIvJL-68eU(~Xbe=RFWG8Kk@2NoiS?*rjxuOKu|_mYgzDR&qDn$`5JKHWC~d|Tk*O=Ms2l$n{gX4fS>uq( z0F}Z?!E%gIFsE~gl70Rth5Cb+V#cuky`cH!glTWWmJ$K00&C*1@VI!8g1FL6VUE-7 zB9FXF{*|bCMX>h3&%B4!&SL`1FS9;PUZg06_DxBgKY+1`;>2!bK}GO*I5NNh1;|Vq z;C3Hp=SyMktiY|L-P46*iXLKqIx&4>ki9Gvz~)?Yfb}v7s1L(AK{+6!lhPvNd&xUR zC3s(cV~4VJr=If3LVlX!|H`_`Dmk z{EPDC-&5f~N|ioSLE7Hj;h#j+KlWZ%NuUXfX|MOwGhkA77xjrDQeb_Dx!$P?bjD*v zSeZS)`SfD?)S7EG_A;@Q-Ict|`@EI6w+sB`#hd>%i&-nK8zMM8;wETR7ou&w^8`O& zCu|&l)>6ut`+zw3_2SReW3)$I-~m6+3?Tj(o62fgV&0j2cn5yewN(uiJ6R;Cet>bH z!3Jv4((NnGm&smImZ9gT;AY6}BPfGeH5YtB^!6JWEjV)@aBixY3hz*|Ho`v$T%^2;;c zJIy`rF@j3yDIe-Khkyk9*=8j#IM567lP8POvbw`Nt*DxH?w`#Jnjz?%cG zi{sZP986JE%wM=qj@*+iw|_-UWO*z-=T?e|8ge(;X5Ny$0s7mH&pb-t4#MFT(n`PJ zv1J+kCIVegiDkhGruw!q_($Li^A2T1jF8@LNL(otrLoSb3RBU8HexA%_O;_eFZa;P z11!$ulV|Q#56Ca3-;59^+B2<&lPudQAircv3k9nF;Rk%Zu9#N)dF{jh>&z_ke?mhV z#?Ds8_O4|A{3~bW;`*=XNLAipRtU*&m5bRO4t{gHAsItTMOjvltN7&8Q3D-Qj%2PmtCJ(6D8Vr(SRq+DLtRW6fd$| z*#>9>cx`qZ0-jnszPl?ZFhtQi+h{BeoFmq|*zixLCQ^!(ZVcwT-}x`%H@ylUfjvPxprtG32%E_S?NyF~0DPshL zWrY;JZ?X$7U_zLfL%ivdH57tX!xNli`1yZK?w+K6lOw+7^Mp&1aO^YSAV+E7rBS<- z^ca>r;zaa3hnya1zC+k+ykppFrt_wBM%|`xbehq1H)L=Ag{g~NA59NGvxxD(X3>AF zApgvse-g9mx-P1pE9Bf=xJ$RuRzzrAt2iymThYORE@qbj2M2r zKjVJ;0z?(Xlt7RE$%QWpHh?=F&Faieo94pox(n3Xh#$>zZbJ%=jli05&PbNUgagMc z;UewFrkJj|16p3Pno*d`T$YPLXY@E^RTWW21p*T(C*1X|nAnJ4$e#9?72vM5P#hyDs&fsQ#)wn3uP) zpu>RVvm0p%Y9f;Vfy|5xCc(Vc>))#G<~>KWxX_)S_02BDzMShua9vH)aQq~+aID(q={Op`xG-|C+&5iXj4KyJ;DdoJ3hkFu}*aCco zU!V0*k|~uFUt;j!Fu>WEOmnO>rszj;<3VYiex~J-xU$h`VFmFh3ro8jRUWkhZnMjt z^-8+aST=oa)mT~A#a7*Od$70rY3jTkCcUhP!SofHHydAI6;5s+2Yf?oxiRCt71wuB z9EfWYU9nze;GTO_(KgNoiqRK9U#)-is90FZ=qN!?98o#(^pJ0$qsssc!;4}!~IaZvd|CD>dbZG`2Uispi0)Ac> zv=4ODmBu=m{k&Gf-_@;O_T4p|OI^19B_vP2L(UdUj=`+K8ekMX&ohpR<59Q5?`Lv$ zNDvkY<{4<&V)nogj#DAdZt0UymO(QY%mw_KvTMXWr`}WszQQ9$uTg-oBZXcb-`g+w zwknBluW`@ra5gsb@PaDuOe@?G?06(Xq{-K&BBGkYw=+yiUU|Ql<1y-LYq1>Y;ng0f zu-3-_4Bz$qMFKSb&=$cn9egzEDOs}@!>lEMFUH#?f7=S+gko;rNdU&&xDb~h$h_-bn`qjr1Enu(i_ zSF+DNg$f;h07iU2^a##ceN`aN{Cl^Cb~M9#QO|I`s#xjA+`VWeZos!@wZf*(kih{I zmIAM`s?$rN1OBVsYG4y-YpjM|153`vpRg^S9Yb0I_`P)r4_f2dPUn-=si8kMGgr6} z&-9REc(YHjX24#xuEDSV-y#TUTa90i%WYFMugWK5kcVU7FQRBa7pZm+f!bhax5YUM z#Z-|YeAcsd>XtNvL5u(b4;O!X1kU%r4b!0N6Ody+6#P;4Y1Cbq`z zfPINGg=!KI(s2A7I_?XZ#Kb~wI2^

Cfl2*u&`AhC0dFC#e}=Y^q;R3iIc7UbE}x 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 - + + + + + + + - - - - - + + + @@ -23,9 +27,9 @@ - + - + @@ -37,9 +41,9 @@ - + - + @@ -78,11 +82,18 @@ - + + + + + + + + diff --git a/pom.xml b/pom.xml index bf7853a2..24547bbc 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ 20090211 1.9.12 4.11 - 1.5 2.3.1 @@ -67,33 +66,24 @@ ${junit.version} test - - com.sun.jersey - jersey-client - ${jersey.version} - test - - src - unittest + ${project.basedir}/target + ${project.build.directory}/classes + ${project.artifactId}-${project.version} + ${project.build.directory}/test-classes + ${project.basedir}/src/main/java + src/main/scripts + ${project.basedir}/src/test/java - com/esri/core/geometry - src/com/esri/core/geometry - - *.txt - + ${project.basedir}/src/main/resources - com/esri/core/geometry - unittest/com/esri/core/geometry - - *.txt - + ${project.basedir}/src/test/resources diff --git a/src/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamBase.java rename to src/main/java/com/esri/core/geometry/AttributeStreamBase.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfDbl.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfFloat.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt16.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt32.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt64.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt8.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java diff --git a/src/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java similarity index 100% rename from src/com/esri/core/geometry/Boundary.java rename to src/main/java/com/esri/core/geometry/Boundary.java diff --git a/src/com/esri/core/geometry/BucketSort.java b/src/main/java/com/esri/core/geometry/BucketSort.java similarity index 100% rename from src/com/esri/core/geometry/BucketSort.java rename to src/main/java/com/esri/core/geometry/BucketSort.java diff --git a/src/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java similarity index 100% rename from src/com/esri/core/geometry/Bufferer.java rename to src/main/java/com/esri/core/geometry/Bufferer.java diff --git a/src/com/esri/core/geometry/ByteBufferCursor.java b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java similarity index 100% rename from src/com/esri/core/geometry/ByteBufferCursor.java rename to src/main/java/com/esri/core/geometry/ByteBufferCursor.java diff --git a/src/com/esri/core/geometry/ClassicSort.java b/src/main/java/com/esri/core/geometry/ClassicSort.java similarity index 100% rename from src/com/esri/core/geometry/ClassicSort.java rename to src/main/java/com/esri/core/geometry/ClassicSort.java diff --git a/src/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java similarity index 100% rename from src/com/esri/core/geometry/Clipper.java rename to src/main/java/com/esri/core/geometry/Clipper.java diff --git a/src/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java similarity index 100% rename from src/com/esri/core/geometry/Clusterer.java rename to src/main/java/com/esri/core/geometry/Clusterer.java diff --git a/src/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java similarity index 100% rename from src/com/esri/core/geometry/ConstructOffset.java rename to src/main/java/com/esri/core/geometry/ConstructOffset.java diff --git a/src/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java similarity index 100% rename from src/com/esri/core/geometry/ConvexHull.java rename to src/main/java/com/esri/core/geometry/ConvexHull.java diff --git a/src/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java similarity index 100% rename from src/com/esri/core/geometry/CrackAndCluster.java rename to src/main/java/com/esri/core/geometry/CrackAndCluster.java diff --git a/src/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java similarity index 100% rename from src/com/esri/core/geometry/Cracker.java rename to src/main/java/com/esri/core/geometry/Cracker.java diff --git a/src/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java similarity index 100% rename from src/com/esri/core/geometry/Cutter.java rename to src/main/java/com/esri/core/geometry/Cutter.java diff --git a/src/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java similarity index 100% rename from src/com/esri/core/geometry/DirtyFlags.java rename to src/main/java/com/esri/core/geometry/DirtyFlags.java diff --git a/src/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java similarity index 100% rename from src/com/esri/core/geometry/ECoordinate.java rename to src/main/java/com/esri/core/geometry/ECoordinate.java diff --git a/src/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java similarity index 100% rename from src/com/esri/core/geometry/EditShape.java rename to src/main/java/com/esri/core/geometry/EditShape.java diff --git a/src/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java similarity index 100% rename from src/com/esri/core/geometry/Envelope.java rename to src/main/java/com/esri/core/geometry/Envelope.java diff --git a/src/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java similarity index 100% rename from src/com/esri/core/geometry/Envelope1D.java rename to src/main/java/com/esri/core/geometry/Envelope1D.java diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java similarity index 100% rename from src/com/esri/core/geometry/Envelope2D.java rename to src/main/java/com/esri/core/geometry/Envelope2D.java diff --git a/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java similarity index 100% rename from src/com/esri/core/geometry/Envelope2DIntersectorImpl.java rename to src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java diff --git a/src/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java similarity index 100% rename from src/com/esri/core/geometry/Envelope3D.java rename to src/main/java/com/esri/core/geometry/Envelope3D.java diff --git a/src/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java similarity index 100% rename from src/com/esri/core/geometry/GeoDist.java rename to src/main/java/com/esri/core/geometry/GeoDist.java diff --git a/src/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/GeoJsonExportFlags.java rename to src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java diff --git a/src/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/GeoJsonImportFlags.java rename to src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java diff --git a/src/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java similarity index 100% rename from src/com/esri/core/geometry/GeodeticCurveType.java rename to src/main/java/com/esri/core/geometry/GeodeticCurveType.java diff --git a/src/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java similarity index 100% rename from src/com/esri/core/geometry/Geometry.java rename to src/main/java/com/esri/core/geometry/Geometry.java diff --git a/src/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java similarity index 100% rename from src/com/esri/core/geometry/GeometryAccelerators.java rename to src/main/java/com/esri/core/geometry/GeometryAccelerators.java diff --git a/src/com/esri/core/geometry/GeometryCursor.java b/src/main/java/com/esri/core/geometry/GeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/GeometryCursor.java rename to src/main/java/com/esri/core/geometry/GeometryCursor.java diff --git a/src/com/esri/core/geometry/GeometryCursorAppend.java b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java similarity index 100% rename from src/com/esri/core/geometry/GeometryCursorAppend.java rename to src/main/java/com/esri/core/geometry/GeometryCursorAppend.java diff --git a/src/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java similarity index 100% rename from src/com/esri/core/geometry/GeometryEngine.java rename to src/main/java/com/esri/core/geometry/GeometryEngine.java diff --git a/src/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java similarity index 100% rename from src/com/esri/core/geometry/GeometryException.java rename to src/main/java/com/esri/core/geometry/GeometryException.java diff --git a/src/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java similarity index 100% rename from src/com/esri/core/geometry/GeometrySerializer.java rename to src/main/java/com/esri/core/geometry/GeometrySerializer.java diff --git a/src/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java similarity index 100% rename from src/com/esri/core/geometry/IndexHashTable.java rename to src/main/java/com/esri/core/geometry/IndexHashTable.java diff --git a/src/com/esri/core/geometry/IndexMultiDCList.java b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java similarity index 100% rename from src/com/esri/core/geometry/IndexMultiDCList.java rename to src/main/java/com/esri/core/geometry/IndexMultiDCList.java diff --git a/src/com/esri/core/geometry/IndexMultiList.java b/src/main/java/com/esri/core/geometry/IndexMultiList.java similarity index 100% rename from src/com/esri/core/geometry/IndexMultiList.java rename to src/main/java/com/esri/core/geometry/IndexMultiList.java diff --git a/src/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java similarity index 100% rename from src/com/esri/core/geometry/InternalUtils.java rename to src/main/java/com/esri/core/geometry/InternalUtils.java diff --git a/src/com/esri/core/geometry/Interop.java b/src/main/java/com/esri/core/geometry/Interop.java similarity index 100% rename from src/com/esri/core/geometry/Interop.java rename to src/main/java/com/esri/core/geometry/Interop.java diff --git a/src/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java similarity index 100% rename from src/com/esri/core/geometry/IntervalTreeImpl.java rename to src/main/java/com/esri/core/geometry/IntervalTreeImpl.java diff --git a/src/com/esri/core/geometry/JSONArrayEnumerator.java b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java similarity index 100% rename from src/com/esri/core/geometry/JSONArrayEnumerator.java rename to src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java diff --git a/src/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java similarity index 100% rename from src/com/esri/core/geometry/JSONObjectEnumerator.java rename to src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java diff --git a/src/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java similarity index 100% rename from src/com/esri/core/geometry/JSONUtils.java rename to src/main/java/com/esri/core/geometry/JSONUtils.java diff --git a/src/com/esri/core/geometry/JsonCursor.java b/src/main/java/com/esri/core/geometry/JsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/JsonCursor.java rename to src/main/java/com/esri/core/geometry/JsonCursor.java diff --git a/src/com/esri/core/geometry/JsonParserCursor.java b/src/main/java/com/esri/core/geometry/JsonParserCursor.java similarity index 100% rename from src/com/esri/core/geometry/JsonParserCursor.java rename to src/main/java/com/esri/core/geometry/JsonParserCursor.java diff --git a/src/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java similarity index 100% rename from src/com/esri/core/geometry/JsonParserReader.java rename to src/main/java/com/esri/core/geometry/JsonParserReader.java diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java similarity index 100% rename from src/com/esri/core/geometry/JsonReader.java rename to src/main/java/com/esri/core/geometry/JsonReader.java diff --git a/src/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java similarity index 100% rename from src/com/esri/core/geometry/JsonStringWriter.java rename to src/main/java/com/esri/core/geometry/JsonStringWriter.java diff --git a/src/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java similarity index 100% rename from src/com/esri/core/geometry/JsonValueReader.java rename to src/main/java/com/esri/core/geometry/JsonValueReader.java diff --git a/src/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java similarity index 100% rename from src/com/esri/core/geometry/JsonWriter.java rename to src/main/java/com/esri/core/geometry/JsonWriter.java diff --git a/src/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java similarity index 100% rename from src/com/esri/core/geometry/Line.java rename to src/main/java/com/esri/core/geometry/Line.java diff --git a/src/com/esri/core/geometry/ListeningGeometryCursor.java b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/ListeningGeometryCursor.java rename to src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java diff --git a/src/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java similarity index 100% rename from src/com/esri/core/geometry/MapGeometry.java rename to src/main/java/com/esri/core/geometry/MapGeometry.java diff --git a/src/com/esri/core/geometry/MapGeometryCursor.java b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/MapGeometryCursor.java rename to src/main/java/com/esri/core/geometry/MapGeometryCursor.java diff --git a/src/com/esri/core/geometry/MapOGCStructure.java b/src/main/java/com/esri/core/geometry/MapOGCStructure.java similarity index 100% rename from src/com/esri/core/geometry/MapOGCStructure.java rename to src/main/java/com/esri/core/geometry/MapOGCStructure.java diff --git a/src/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java similarity index 100% rename from src/com/esri/core/geometry/MathUtils.java rename to src/main/java/com/esri/core/geometry/MathUtils.java diff --git a/src/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java similarity index 100% rename from src/com/esri/core/geometry/MgrsConversionMode.java rename to src/main/java/com/esri/core/geometry/MgrsConversionMode.java diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java similarity index 100% rename from src/com/esri/core/geometry/MultiPath.java rename to src/main/java/com/esri/core/geometry/MultiPath.java diff --git a/src/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java similarity index 100% rename from src/com/esri/core/geometry/MultiPathImpl.java rename to src/main/java/com/esri/core/geometry/MultiPathImpl.java diff --git a/src/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java similarity index 100% rename from src/com/esri/core/geometry/MultiPoint.java rename to src/main/java/com/esri/core/geometry/MultiPoint.java diff --git a/src/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java similarity index 100% rename from src/com/esri/core/geometry/MultiPointImpl.java rename to src/main/java/com/esri/core/geometry/MultiPointImpl.java diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java similarity index 100% rename from src/com/esri/core/geometry/MultiVertexGeometry.java rename to src/main/java/com/esri/core/geometry/MultiVertexGeometry.java diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java similarity index 100% rename from src/com/esri/core/geometry/MultiVertexGeometryImpl.java rename to src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java diff --git a/src/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java similarity index 100% rename from src/com/esri/core/geometry/NonSimpleResult.java rename to src/main/java/com/esri/core/geometry/NonSimpleResult.java diff --git a/src/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java similarity index 100% rename from src/com/esri/core/geometry/NumberUtils.java rename to src/main/java/com/esri/core/geometry/NumberUtils.java diff --git a/src/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java similarity index 100% rename from src/com/esri/core/geometry/OGCStructure.java rename to src/main/java/com/esri/core/geometry/OGCStructure.java diff --git a/src/com/esri/core/geometry/ObjectCacheTable.java b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java similarity index 100% rename from src/com/esri/core/geometry/ObjectCacheTable.java rename to src/main/java/com/esri/core/geometry/ObjectCacheTable.java diff --git a/src/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java similarity index 100% rename from src/com/esri/core/geometry/Operator.java rename to src/main/java/com/esri/core/geometry/Operator.java diff --git a/src/com/esri/core/geometry/OperatorBoundary.java b/src/main/java/com/esri/core/geometry/OperatorBoundary.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBoundary.java rename to src/main/java/com/esri/core/geometry/OperatorBoundary.java diff --git a/src/com/esri/core/geometry/OperatorBoundaryLocal.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBoundaryLocal.java rename to src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java diff --git a/src/com/esri/core/geometry/OperatorBoundaryLocalCursor.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBoundaryLocalCursor.java rename to src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java diff --git a/src/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBuffer.java rename to src/main/java/com/esri/core/geometry/OperatorBuffer.java diff --git a/src/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBufferCursor.java rename to src/main/java/com/esri/core/geometry/OperatorBufferCursor.java diff --git a/src/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBufferLocal.java rename to src/main/java/com/esri/core/geometry/OperatorBufferLocal.java diff --git a/src/com/esri/core/geometry/OperatorClip.java b/src/main/java/com/esri/core/geometry/OperatorClip.java similarity index 100% rename from src/com/esri/core/geometry/OperatorClip.java rename to src/main/java/com/esri/core/geometry/OperatorClip.java diff --git a/src/com/esri/core/geometry/OperatorClipCursor.java b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorClipCursor.java rename to src/main/java/com/esri/core/geometry/OperatorClipCursor.java diff --git a/src/com/esri/core/geometry/OperatorClipLocal.java b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorClipLocal.java rename to src/main/java/com/esri/core/geometry/OperatorClipLocal.java diff --git a/src/com/esri/core/geometry/OperatorContains.java b/src/main/java/com/esri/core/geometry/OperatorContains.java similarity index 100% rename from src/com/esri/core/geometry/OperatorContains.java rename to src/main/java/com/esri/core/geometry/OperatorContains.java diff --git a/src/com/esri/core/geometry/OperatorContainsLocal.java b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorContainsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorContainsLocal.java diff --git a/src/com/esri/core/geometry/OperatorConvexHull.java b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java similarity index 100% rename from src/com/esri/core/geometry/OperatorConvexHull.java rename to src/main/java/com/esri/core/geometry/OperatorConvexHull.java diff --git a/src/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorConvexHullCursor.java rename to src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java diff --git a/src/com/esri/core/geometry/OperatorConvexHullLocal.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorConvexHullLocal.java rename to src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java diff --git a/src/com/esri/core/geometry/OperatorCrosses.java b/src/main/java/com/esri/core/geometry/OperatorCrosses.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCrosses.java rename to src/main/java/com/esri/core/geometry/OperatorCrosses.java diff --git a/src/com/esri/core/geometry/OperatorCrossesLocal.java b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCrossesLocal.java rename to src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java diff --git a/src/com/esri/core/geometry/OperatorCut.java b/src/main/java/com/esri/core/geometry/OperatorCut.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCut.java rename to src/main/java/com/esri/core/geometry/OperatorCut.java diff --git a/src/com/esri/core/geometry/OperatorCutCursor.java b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCutCursor.java rename to src/main/java/com/esri/core/geometry/OperatorCutCursor.java diff --git a/src/com/esri/core/geometry/OperatorCutLocal.java b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCutLocal.java rename to src/main/java/com/esri/core/geometry/OperatorCutLocal.java diff --git a/src/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDensifyByLength.java rename to src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java diff --git a/src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java rename to src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java diff --git a/src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java diff --git a/src/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDifference.java rename to src/main/java/com/esri/core/geometry/OperatorDifference.java diff --git a/src/com/esri/core/geometry/OperatorDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDifferenceCursor.java rename to src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java diff --git a/src/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDifferenceLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java diff --git a/src/com/esri/core/geometry/OperatorDisjoint.java b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDisjoint.java rename to src/main/java/com/esri/core/geometry/OperatorDisjoint.java diff --git a/src/com/esri/core/geometry/OperatorDisjointLocal.java b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDisjointLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java diff --git a/src/com/esri/core/geometry/OperatorDistance.java b/src/main/java/com/esri/core/geometry/OperatorDistance.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDistance.java rename to src/main/java/com/esri/core/geometry/OperatorDistance.java diff --git a/src/com/esri/core/geometry/OperatorDistanceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDistanceLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java diff --git a/src/com/esri/core/geometry/OperatorEquals.java b/src/main/java/com/esri/core/geometry/OperatorEquals.java similarity index 100% rename from src/com/esri/core/geometry/OperatorEquals.java rename to src/main/java/com/esri/core/geometry/OperatorEquals.java diff --git a/src/com/esri/core/geometry/OperatorEqualsLocal.java b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorEqualsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToESRIShape.java rename to src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java rename to src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToGeoJson.java rename to src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java rename to src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToJson.java rename to src/main/java/com/esri/core/geometry/OperatorExportToJson.java diff --git a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToJsonCursor.java rename to src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToWkb.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWkb.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWkb.java diff --git a/src/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWkbLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToWkt.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWkt.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWkt.java diff --git a/src/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWktLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java diff --git a/src/com/esri/core/geometry/OperatorFactory.java b/src/main/java/com/esri/core/geometry/OperatorFactory.java similarity index 100% rename from src/com/esri/core/geometry/OperatorFactory.java rename to src/main/java/com/esri/core/geometry/OperatorFactory.java diff --git a/src/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorFactoryLocal.java rename to src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java diff --git a/src/com/esri/core/geometry/OperatorGeneralize.java b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeneralize.java rename to src/main/java/com/esri/core/geometry/OperatorGeneralize.java diff --git a/src/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeneralizeCursor.java rename to src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java diff --git a/src/com/esri/core/geometry/OperatorGeneralizeLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeneralizeLocal.java rename to src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticArea.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticArea.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticLength.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromESRIShape.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromGeoJson.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromJson.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromJson.java diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromJsonCursor.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWkb.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWkb.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWkbLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWkt.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWkt.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWktLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java diff --git a/src/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java similarity index 100% rename from src/com/esri/core/geometry/OperatorInternalRelationUtils.java rename to src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java diff --git a/src/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersection.java rename to src/main/java/com/esri/core/geometry/OperatorIntersection.java diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersectionCursor.java rename to src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java diff --git a/src/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersectionLocal.java rename to src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java diff --git a/src/com/esri/core/geometry/OperatorIntersects.java b/src/main/java/com/esri/core/geometry/OperatorIntersects.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersects.java rename to src/main/java/com/esri/core/geometry/OperatorIntersects.java diff --git a/src/com/esri/core/geometry/OperatorIntersectsLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersectsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java diff --git a/src/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOffset.java rename to src/main/java/com/esri/core/geometry/OperatorOffset.java diff --git a/src/com/esri/core/geometry/OperatorOffsetCursor.java b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOffsetCursor.java rename to src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java diff --git a/src/com/esri/core/geometry/OperatorOffsetLocal.java b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOffsetLocal.java rename to src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java diff --git a/src/com/esri/core/geometry/OperatorOverlaps.java b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOverlaps.java rename to src/main/java/com/esri/core/geometry/OperatorOverlaps.java diff --git a/src/com/esri/core/geometry/OperatorOverlapsLocal.java b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOverlapsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java diff --git a/src/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProject.java rename to src/main/java/com/esri/core/geometry/OperatorProject.java diff --git a/src/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProjectLocal.java rename to src/main/java/com/esri/core/geometry/OperatorProjectLocal.java diff --git a/src/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProximity2D.java rename to src/main/java/com/esri/core/geometry/OperatorProximity2D.java diff --git a/src/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProximity2DLocal.java rename to src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java diff --git a/src/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java similarity index 100% rename from src/com/esri/core/geometry/OperatorRelate.java rename to src/main/java/com/esri/core/geometry/OperatorRelate.java diff --git a/src/com/esri/core/geometry/OperatorRelateLocal.java b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorRelateLocal.java rename to src/main/java/com/esri/core/geometry/OperatorRelateLocal.java diff --git a/src/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimpleRelation.java rename to src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java diff --git a/src/com/esri/core/geometry/OperatorSimplify.java b/src/main/java/com/esri/core/geometry/OperatorSimplify.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplify.java rename to src/main/java/com/esri/core/geometry/OperatorSimplify.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyCursor.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyCursor.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyLocal.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyOGC.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSymmetricDifference.java rename to src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java rename to src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java rename to src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java diff --git a/src/com/esri/core/geometry/OperatorTouches.java b/src/main/java/com/esri/core/geometry/OperatorTouches.java similarity index 100% rename from src/com/esri/core/geometry/OperatorTouches.java rename to src/main/java/com/esri/core/geometry/OperatorTouches.java diff --git a/src/com/esri/core/geometry/OperatorTouchesLocal.java b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorTouchesLocal.java rename to src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java diff --git a/src/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java similarity index 100% rename from src/com/esri/core/geometry/OperatorUnion.java rename to src/main/java/com/esri/core/geometry/OperatorUnion.java diff --git a/src/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorUnionCursor.java rename to src/main/java/com/esri/core/geometry/OperatorUnionCursor.java diff --git a/src/com/esri/core/geometry/OperatorUnionLocal.java b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorUnionLocal.java rename to src/main/java/com/esri/core/geometry/OperatorUnionLocal.java diff --git a/src/com/esri/core/geometry/OperatorWithin.java b/src/main/java/com/esri/core/geometry/OperatorWithin.java similarity index 100% rename from src/com/esri/core/geometry/OperatorWithin.java rename to src/main/java/com/esri/core/geometry/OperatorWithin.java diff --git a/src/com/esri/core/geometry/OperatorWithinLocal.java b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorWithinLocal.java rename to src/main/java/com/esri/core/geometry/OperatorWithinLocal.java diff --git a/src/com/esri/core/geometry/PathFlags.java b/src/main/java/com/esri/core/geometry/PathFlags.java similarity index 100% rename from src/com/esri/core/geometry/PathFlags.java rename to src/main/java/com/esri/core/geometry/PathFlags.java diff --git a/src/com/esri/core/geometry/PeDouble.java b/src/main/java/com/esri/core/geometry/PeDouble.java similarity index 100% rename from src/com/esri/core/geometry/PeDouble.java rename to src/main/java/com/esri/core/geometry/PeDouble.java diff --git a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java similarity index 100% rename from src/com/esri/core/geometry/PlaneSweepCrackerHelper.java rename to src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java diff --git a/src/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java similarity index 100% rename from src/com/esri/core/geometry/Point.java rename to src/main/java/com/esri/core/geometry/Point.java diff --git a/src/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java similarity index 100% rename from src/com/esri/core/geometry/Point2D.java rename to src/main/java/com/esri/core/geometry/Point2D.java diff --git a/src/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java similarity index 100% rename from src/com/esri/core/geometry/Point3D.java rename to src/main/java/com/esri/core/geometry/Point3D.java diff --git a/src/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java similarity index 100% rename from src/com/esri/core/geometry/PointInPolygonHelper.java rename to src/main/java/com/esri/core/geometry/PointInPolygonHelper.java diff --git a/src/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java similarity index 100% rename from src/com/esri/core/geometry/Polygon.java rename to src/main/java/com/esri/core/geometry/Polygon.java diff --git a/src/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java similarity index 100% rename from src/com/esri/core/geometry/PolygonUtils.java rename to src/main/java/com/esri/core/geometry/PolygonUtils.java diff --git a/src/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java similarity index 100% rename from src/com/esri/core/geometry/Polyline.java rename to src/main/java/com/esri/core/geometry/Polyline.java diff --git a/src/com/esri/core/geometry/PolylinePath.java b/src/main/java/com/esri/core/geometry/PolylinePath.java similarity index 100% rename from src/com/esri/core/geometry/PolylinePath.java rename to src/main/java/com/esri/core/geometry/PolylinePath.java diff --git a/src/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java similarity index 100% rename from src/com/esri/core/geometry/ProgressTracker.java rename to src/main/java/com/esri/core/geometry/ProgressTracker.java diff --git a/src/com/esri/core/geometry/ProjectionTransformation.java b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java similarity index 100% rename from src/com/esri/core/geometry/ProjectionTransformation.java rename to src/main/java/com/esri/core/geometry/ProjectionTransformation.java diff --git a/src/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java similarity index 100% rename from src/com/esri/core/geometry/Proximity2DResult.java rename to src/main/java/com/esri/core/geometry/Proximity2DResult.java diff --git a/src/com/esri/core/geometry/Proximity2DResultComparator.java b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java similarity index 100% rename from src/com/esri/core/geometry/Proximity2DResultComparator.java rename to src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java diff --git a/src/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java similarity index 100% rename from src/com/esri/core/geometry/QuadTree.java rename to src/main/java/com/esri/core/geometry/QuadTree.java diff --git a/src/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java similarity index 100% rename from src/com/esri/core/geometry/QuadTreeImpl.java rename to src/main/java/com/esri/core/geometry/QuadTreeImpl.java diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java similarity index 100% rename from src/com/esri/core/geometry/RasterizedGeometry2D.java rename to src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java similarity index 100% rename from src/com/esri/core/geometry/RasterizedGeometry2DImpl.java rename to src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java diff --git a/src/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java similarity index 100% rename from src/com/esri/core/geometry/RelationalOperations.java rename to src/main/java/com/esri/core/geometry/RelationalOperations.java diff --git a/src/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java similarity index 100% rename from src/com/esri/core/geometry/RelationalOperationsMatrix.java rename to src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java diff --git a/src/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java similarity index 100% rename from src/com/esri/core/geometry/RingOrientationFixer.java rename to src/main/java/com/esri/core/geometry/RingOrientationFixer.java diff --git a/src/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java similarity index 100% rename from src/com/esri/core/geometry/Segment.java rename to src/main/java/com/esri/core/geometry/Segment.java diff --git a/src/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java similarity index 100% rename from src/com/esri/core/geometry/SegmentBuffer.java rename to src/main/java/com/esri/core/geometry/SegmentBuffer.java diff --git a/src/com/esri/core/geometry/SegmentFlags.java b/src/main/java/com/esri/core/geometry/SegmentFlags.java similarity index 100% rename from src/com/esri/core/geometry/SegmentFlags.java rename to src/main/java/com/esri/core/geometry/SegmentFlags.java diff --git a/src/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java similarity index 100% rename from src/com/esri/core/geometry/SegmentIntersector.java rename to src/main/java/com/esri/core/geometry/SegmentIntersector.java diff --git a/src/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java similarity index 100% rename from src/com/esri/core/geometry/SegmentIterator.java rename to src/main/java/com/esri/core/geometry/SegmentIterator.java diff --git a/src/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java similarity index 100% rename from src/com/esri/core/geometry/SegmentIteratorImpl.java rename to src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java diff --git a/src/com/esri/core/geometry/ShapeExportFlags.java b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/ShapeExportFlags.java rename to src/main/java/com/esri/core/geometry/ShapeExportFlags.java diff --git a/src/com/esri/core/geometry/ShapeImportFlags.java b/src/main/java/com/esri/core/geometry/ShapeImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/ShapeImportFlags.java rename to src/main/java/com/esri/core/geometry/ShapeImportFlags.java diff --git a/src/com/esri/core/geometry/ShapeModifiers.java b/src/main/java/com/esri/core/geometry/ShapeModifiers.java similarity index 100% rename from src/com/esri/core/geometry/ShapeModifiers.java rename to src/main/java/com/esri/core/geometry/ShapeModifiers.java diff --git a/src/com/esri/core/geometry/ShapeType.java b/src/main/java/com/esri/core/geometry/ShapeType.java similarity index 100% rename from src/com/esri/core/geometry/ShapeType.java rename to src/main/java/com/esri/core/geometry/ShapeType.java diff --git a/src/com/esri/core/geometry/SimpleByteBufferCursor.java b/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleByteBufferCursor.java rename to src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java diff --git a/src/com/esri/core/geometry/SimpleGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleGeometryCursor.java rename to src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java diff --git a/src/com/esri/core/geometry/SimpleJsonCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleJsonCursor.java rename to src/main/java/com/esri/core/geometry/SimpleJsonCursor.java diff --git a/src/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleJsonParserCursor.java rename to src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java diff --git a/src/com/esri/core/geometry/SimpleMapGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleMapGeometryCursor.java rename to src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java diff --git a/src/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java similarity index 100% rename from src/com/esri/core/geometry/SimpleRasterizer.java rename to src/main/java/com/esri/core/geometry/SimpleRasterizer.java diff --git a/src/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java similarity index 100% rename from src/com/esri/core/geometry/Simplificator.java rename to src/main/java/com/esri/core/geometry/Simplificator.java diff --git a/src/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java similarity index 100% rename from src/com/esri/core/geometry/SpatialReference.java rename to src/main/java/com/esri/core/geometry/SpatialReference.java diff --git a/src/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java similarity index 100% rename from src/com/esri/core/geometry/SpatialReferenceImpl.java rename to src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java diff --git a/src/com/esri/core/geometry/SpatialReferenceSerializer.java b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java similarity index 100% rename from src/com/esri/core/geometry/SpatialReferenceSerializer.java rename to src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java diff --git a/src/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java similarity index 100% rename from src/com/esri/core/geometry/StridedIndexTypeCollection.java rename to src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java diff --git a/src/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java similarity index 100% rename from src/com/esri/core/geometry/StringUtils.java rename to src/main/java/com/esri/core/geometry/StringUtils.java diff --git a/src/com/esri/core/geometry/SweepComparator.java b/src/main/java/com/esri/core/geometry/SweepComparator.java similarity index 100% rename from src/com/esri/core/geometry/SweepComparator.java rename to src/main/java/com/esri/core/geometry/SweepComparator.java diff --git a/src/com/esri/core/geometry/SweepMonkierComparator.java b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java similarity index 100% rename from src/com/esri/core/geometry/SweepMonkierComparator.java rename to src/main/java/com/esri/core/geometry/SweepMonkierComparator.java diff --git a/src/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java similarity index 100% rename from src/com/esri/core/geometry/TopoGraph.java rename to src/main/java/com/esri/core/geometry/TopoGraph.java diff --git a/src/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java similarity index 100% rename from src/com/esri/core/geometry/TopologicalOperations.java rename to src/main/java/com/esri/core/geometry/TopologicalOperations.java diff --git a/src/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java similarity index 100% rename from src/com/esri/core/geometry/Transformation2D.java rename to src/main/java/com/esri/core/geometry/Transformation2D.java diff --git a/src/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java similarity index 100% rename from src/com/esri/core/geometry/Transformation3D.java rename to src/main/java/com/esri/core/geometry/Transformation3D.java diff --git a/src/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java similarity index 100% rename from src/com/esri/core/geometry/Treap.java rename to src/main/java/com/esri/core/geometry/Treap.java diff --git a/src/com/esri/core/geometry/UserCancelException.java b/src/main/java/com/esri/core/geometry/UserCancelException.java similarity index 100% rename from src/com/esri/core/geometry/UserCancelException.java rename to src/main/java/com/esri/core/geometry/UserCancelException.java diff --git a/src/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java similarity index 100% rename from src/com/esri/core/geometry/VertexDescription.java rename to src/main/java/com/esri/core/geometry/VertexDescription.java diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java similarity index 100% rename from src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java rename to src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java diff --git a/src/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java similarity index 100% rename from src/com/esri/core/geometry/VertexDescriptionHash.java rename to src/main/java/com/esri/core/geometry/VertexDescriptionHash.java diff --git a/src/com/esri/core/geometry/WkbByteOrder.java b/src/main/java/com/esri/core/geometry/WkbByteOrder.java similarity index 100% rename from src/com/esri/core/geometry/WkbByteOrder.java rename to src/main/java/com/esri/core/geometry/WkbByteOrder.java diff --git a/src/com/esri/core/geometry/WkbExportFlags.java b/src/main/java/com/esri/core/geometry/WkbExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WkbExportFlags.java rename to src/main/java/com/esri/core/geometry/WkbExportFlags.java diff --git a/src/com/esri/core/geometry/WkbGeometryType.java b/src/main/java/com/esri/core/geometry/WkbGeometryType.java similarity index 100% rename from src/com/esri/core/geometry/WkbGeometryType.java rename to src/main/java/com/esri/core/geometry/WkbGeometryType.java diff --git a/src/com/esri/core/geometry/WkbImportFlags.java b/src/main/java/com/esri/core/geometry/WkbImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WkbImportFlags.java rename to src/main/java/com/esri/core/geometry/WkbImportFlags.java diff --git a/src/com/esri/core/geometry/Wkid.java b/src/main/java/com/esri/core/geometry/Wkid.java similarity index 100% rename from src/com/esri/core/geometry/Wkid.java rename to src/main/java/com/esri/core/geometry/Wkid.java diff --git a/src/com/esri/core/geometry/Wkt.java b/src/main/java/com/esri/core/geometry/Wkt.java similarity index 100% rename from src/com/esri/core/geometry/Wkt.java rename to src/main/java/com/esri/core/geometry/Wkt.java diff --git a/src/com/esri/core/geometry/WktExportFlags.java b/src/main/java/com/esri/core/geometry/WktExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WktExportFlags.java rename to src/main/java/com/esri/core/geometry/WktExportFlags.java diff --git a/src/com/esri/core/geometry/WktImportFlags.java b/src/main/java/com/esri/core/geometry/WktImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WktImportFlags.java rename to src/main/java/com/esri/core/geometry/WktImportFlags.java diff --git a/src/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java similarity index 100% rename from src/com/esri/core/geometry/WktParser.java rename to src/main/java/com/esri/core/geometry/WktParser.java diff --git a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java rename to src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java diff --git a/src/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCCurve.java rename to src/main/java/com/esri/core/geometry/ogc/OGCCurve.java diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCGeometry.java rename to src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java diff --git a/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCGeometryCollection.java rename to src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java diff --git a/src/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCLineString.java rename to src/main/java/com/esri/core/geometry/ogc/OGCLineString.java diff --git a/src/com/esri/core/geometry/ogc/OGCLinearRing.java b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCLinearRing.java rename to src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiCurve.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiLineString.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiPoint.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiPolygon.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiSurface.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java diff --git a/src/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCPoint.java rename to src/main/java/com/esri/core/geometry/ogc/OGCPoint.java diff --git a/src/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCPolygon.java rename to src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java diff --git a/src/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCSurface.java rename to src/main/java/com/esri/core/geometry/ogc/OGCSurface.java diff --git a/src/com/esri/core/geometry/gcs_id_to_tolerance.txt b/src/main/resources/com/esri/core/geometry/gcs_id_to_tolerance.txt similarity index 100% rename from src/com/esri/core/geometry/gcs_id_to_tolerance.txt rename to src/main/resources/com/esri/core/geometry/gcs_id_to_tolerance.txt diff --git a/src/com/esri/core/geometry/gcs_tolerances.txt b/src/main/resources/com/esri/core/geometry/gcs_tolerances.txt similarity index 100% rename from src/com/esri/core/geometry/gcs_tolerances.txt rename to src/main/resources/com/esri/core/geometry/gcs_tolerances.txt diff --git a/src/com/esri/core/geometry/intermediate_to_old_wkid.txt b/src/main/resources/com/esri/core/geometry/intermediate_to_old_wkid.txt similarity index 100% rename from src/com/esri/core/geometry/intermediate_to_old_wkid.txt rename to src/main/resources/com/esri/core/geometry/intermediate_to_old_wkid.txt diff --git a/src/com/esri/core/geometry/new_to_old_wkid.txt b/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt similarity index 100% rename from src/com/esri/core/geometry/new_to_old_wkid.txt rename to src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt diff --git a/src/com/esri/core/geometry/pcs_id_to_tolerance.txt b/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt similarity index 100% rename from src/com/esri/core/geometry/pcs_id_to_tolerance.txt rename to src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt diff --git a/src/com/esri/core/geometry/pcs_tolerances.txt b/src/main/resources/com/esri/core/geometry/pcs_tolerances.txt similarity index 100% rename from src/com/esri/core/geometry/pcs_tolerances.txt rename to src/main/resources/com/esri/core/geometry/pcs_tolerances.txt diff --git a/unittest/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java similarity index 100% rename from unittest/com/esri/core/geometry/GeometryUtils.java rename to src/test/java/com/esri/core/geometry/GeometryUtils.java diff --git a/unittest/com/esri/core/geometry/RandomCoordinateGenerator.java b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java similarity index 100% rename from unittest/com/esri/core/geometry/RandomCoordinateGenerator.java rename to src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java diff --git a/unittest/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java similarity index 100% rename from unittest/com/esri/core/geometry/TestAttributes.java rename to src/test/java/com/esri/core/geometry/TestAttributes.java diff --git a/unittest/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java similarity index 100% rename from unittest/com/esri/core/geometry/TestBuffer.java rename to src/test/java/com/esri/core/geometry/TestBuffer.java diff --git a/unittest/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java similarity index 100% rename from unittest/com/esri/core/geometry/TestClip.java rename to src/test/java/com/esri/core/geometry/TestClip.java diff --git a/unittest/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java similarity index 100% rename from unittest/com/esri/core/geometry/TestCommonMethods.java rename to src/test/java/com/esri/core/geometry/TestCommonMethods.java diff --git a/unittest/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java similarity index 100% rename from unittest/com/esri/core/geometry/TestContains.java rename to src/test/java/com/esri/core/geometry/TestContains.java diff --git a/unittest/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java similarity index 100% rename from unittest/com/esri/core/geometry/TestConvexHull.java rename to src/test/java/com/esri/core/geometry/TestConvexHull.java diff --git a/unittest/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java similarity index 100% rename from unittest/com/esri/core/geometry/TestCut.java rename to src/test/java/com/esri/core/geometry/TestCut.java diff --git a/unittest/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java similarity index 100% rename from unittest/com/esri/core/geometry/TestDifference.java rename to src/test/java/com/esri/core/geometry/TestDifference.java diff --git a/unittest/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java similarity index 100% rename from unittest/com/esri/core/geometry/TestDistance.java rename to src/test/java/com/esri/core/geometry/TestDistance.java diff --git a/unittest/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java similarity index 100% rename from unittest/com/esri/core/geometry/TestEditShape.java rename to src/test/java/com/esri/core/geometry/TestEditShape.java diff --git a/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java similarity index 100% rename from unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java rename to src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java diff --git a/unittest/com/esri/core/geometry/TestEquals.java b/src/test/java/com/esri/core/geometry/TestEquals.java similarity index 100% rename from unittest/com/esri/core/geometry/TestEquals.java rename to src/test/java/com/esri/core/geometry/TestEquals.java diff --git a/unittest/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java similarity index 100% rename from unittest/com/esri/core/geometry/TestFailed.java rename to src/test/java/com/esri/core/geometry/TestFailed.java diff --git a/unittest/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeneralize.java rename to src/test/java/com/esri/core/geometry/TestGeneralize.java diff --git a/unittest/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeodetic.java rename to src/test/java/com/esri/core/geometry/TestGeodetic.java diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeomToGeoJson.java rename to src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java rename to src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java diff --git a/unittest/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java similarity index 100% rename from unittest/com/esri/core/geometry/TestImportExport.java rename to src/test/java/com/esri/core/geometry/TestImportExport.java diff --git a/unittest/com/esri/core/geometry/TestInterpolateAttributes.java b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java similarity index 100% rename from unittest/com/esri/core/geometry/TestInterpolateAttributes.java rename to src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java diff --git a/unittest/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java similarity index 100% rename from unittest/com/esri/core/geometry/TestIntersect2.java rename to src/test/java/com/esri/core/geometry/TestIntersect2.java diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java similarity index 100% rename from unittest/com/esri/core/geometry/TestIntersection.java rename to src/test/java/com/esri/core/geometry/TestIntersection.java diff --git a/unittest/com/esri/core/geometry/TestIntervalTree.java b/src/test/java/com/esri/core/geometry/TestIntervalTree.java similarity index 100% rename from unittest/com/esri/core/geometry/TestIntervalTree.java rename to src/test/java/com/esri/core/geometry/TestIntervalTree.java diff --git a/unittest/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java similarity index 100% rename from unittest/com/esri/core/geometry/TestJSonGeometry.java rename to src/test/java/com/esri/core/geometry/TestJSonGeometry.java diff --git a/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java similarity index 100% rename from unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java rename to src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java diff --git a/unittest/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java similarity index 100% rename from unittest/com/esri/core/geometry/TestJsonParser.java rename to src/test/java/com/esri/core/geometry/TestJsonParser.java diff --git a/unittest/com/esri/core/geometry/TestMathUtils.java b/src/test/java/com/esri/core/geometry/TestMathUtils.java similarity index 100% rename from unittest/com/esri/core/geometry/TestMathUtils.java rename to src/test/java/com/esri/core/geometry/TestMathUtils.java diff --git a/unittest/com/esri/core/geometry/TestMultiPoint.java b/src/test/java/com/esri/core/geometry/TestMultiPoint.java similarity index 100% rename from unittest/com/esri/core/geometry/TestMultiPoint.java rename to src/test/java/com/esri/core/geometry/TestMultiPoint.java diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java similarity index 100% rename from unittest/com/esri/core/geometry/TestOGC.java rename to src/test/java/com/esri/core/geometry/TestOGC.java diff --git a/unittest/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java similarity index 100% rename from unittest/com/esri/core/geometry/TestOffset.java rename to src/test/java/com/esri/core/geometry/TestOffset.java diff --git a/unittest/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java similarity index 100% rename from unittest/com/esri/core/geometry/TestPoint.java rename to src/test/java/com/esri/core/geometry/TestPoint.java diff --git a/unittest/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java similarity index 100% rename from unittest/com/esri/core/geometry/TestPolygon.java rename to src/test/java/com/esri/core/geometry/TestPolygon.java diff --git a/unittest/com/esri/core/geometry/TestPolygonUtils.java b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java similarity index 100% rename from unittest/com/esri/core/geometry/TestPolygonUtils.java rename to src/test/java/com/esri/core/geometry/TestPolygonUtils.java diff --git a/unittest/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java similarity index 100% rename from unittest/com/esri/core/geometry/TestProximity2D.java rename to src/test/java/com/esri/core/geometry/TestProximity2D.java diff --git a/unittest/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java similarity index 100% rename from unittest/com/esri/core/geometry/TestQuadTree.java rename to src/test/java/com/esri/core/geometry/TestQuadTree.java diff --git a/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java similarity index 100% rename from unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java rename to src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java diff --git a/unittest/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java similarity index 100% rename from unittest/com/esri/core/geometry/TestRelation.java rename to src/test/java/com/esri/core/geometry/TestRelation.java diff --git a/unittest/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java similarity index 100% rename from unittest/com/esri/core/geometry/TestSerialization.java rename to src/test/java/com/esri/core/geometry/TestSerialization.java diff --git a/unittest/com/esri/core/geometry/TestShapePreserving.java b/src/test/java/com/esri/core/geometry/TestShapePreserving.java similarity index 100% rename from unittest/com/esri/core/geometry/TestShapePreserving.java rename to src/test/java/com/esri/core/geometry/TestShapePreserving.java diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java similarity index 100% rename from unittest/com/esri/core/geometry/TestSimplify.java rename to src/test/java/com/esri/core/geometry/TestSimplify.java diff --git a/unittest/com/esri/core/geometry/TestTouch.java b/src/test/java/com/esri/core/geometry/TestTouch.java similarity index 100% rename from unittest/com/esri/core/geometry/TestTouch.java rename to src/test/java/com/esri/core/geometry/TestTouch.java diff --git a/unittest/com/esri/core/geometry/TestTreap.java b/src/test/java/com/esri/core/geometry/TestTreap.java similarity index 100% rename from unittest/com/esri/core/geometry/TestTreap.java rename to src/test/java/com/esri/core/geometry/TestTreap.java diff --git a/unittest/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java similarity index 100% rename from unittest/com/esri/core/geometry/TestUnion.java rename to src/test/java/com/esri/core/geometry/TestUnion.java diff --git a/unittest/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWKBSupport.java rename to src/test/java/com/esri/core/geometry/TestWKBSupport.java diff --git a/unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java rename to src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java diff --git a/unittest/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWkid.java rename to src/test/java/com/esri/core/geometry/TestWkid.java diff --git a/unittest/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWktParser.java rename to src/test/java/com/esri/core/geometry/TestWktParser.java diff --git a/unittest/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java similarity index 100% rename from unittest/com/esri/core/geometry/Utils.java rename to src/test/java/com/esri/core/geometry/Utils.java diff --git a/unittest/com/esri/core/geometry/savedAngularUnit.txt b/src/test/resources/com/esri/core/geometry/savedAngularUnit.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedAngularUnit.txt rename to src/test/resources/com/esri/core/geometry/savedAngularUnit.txt diff --git a/unittest/com/esri/core/geometry/savedAreaUnit.txt b/src/test/resources/com/esri/core/geometry/savedAreaUnit.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedAreaUnit.txt rename to src/test/resources/com/esri/core/geometry/savedAreaUnit.txt diff --git a/unittest/com/esri/core/geometry/savedEnvelope.txt b/src/test/resources/com/esri/core/geometry/savedEnvelope.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedEnvelope.txt rename to src/test/resources/com/esri/core/geometry/savedEnvelope.txt diff --git a/unittest/com/esri/core/geometry/savedEnvelope2D.txt b/src/test/resources/com/esri/core/geometry/savedEnvelope2D.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedEnvelope2D.txt rename to src/test/resources/com/esri/core/geometry/savedEnvelope2D.txt diff --git a/unittest/com/esri/core/geometry/savedLinearUnit.txt b/src/test/resources/com/esri/core/geometry/savedLinearUnit.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedLinearUnit.txt rename to src/test/resources/com/esri/core/geometry/savedLinearUnit.txt diff --git a/unittest/com/esri/core/geometry/savedMultiPoint.txt b/src/test/resources/com/esri/core/geometry/savedMultiPoint.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedMultiPoint.txt rename to src/test/resources/com/esri/core/geometry/savedMultiPoint.txt diff --git a/unittest/com/esri/core/geometry/savedPoint.txt b/src/test/resources/com/esri/core/geometry/savedPoint.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedPoint.txt rename to src/test/resources/com/esri/core/geometry/savedPoint.txt diff --git a/unittest/com/esri/core/geometry/savedPolygon.txt b/src/test/resources/com/esri/core/geometry/savedPolygon.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedPolygon.txt rename to src/test/resources/com/esri/core/geometry/savedPolygon.txt diff --git a/unittest/com/esri/core/geometry/savedPolyline.txt b/src/test/resources/com/esri/core/geometry/savedPolyline.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedPolyline.txt rename to src/test/resources/com/esri/core/geometry/savedPolyline.txt diff --git a/unittest/com/esri/core/geometry/savedProjectionTransformation.txt b/src/test/resources/com/esri/core/geometry/savedProjectionTransformation.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedProjectionTransformation.txt rename to src/test/resources/com/esri/core/geometry/savedProjectionTransformation.txt diff --git a/unittest/com/esri/core/geometry/savedSpatialReference.txt b/src/test/resources/com/esri/core/geometry/savedSpatialReference.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedSpatialReference.txt rename to src/test/resources/com/esri/core/geometry/savedSpatialReference.txt From 4f92c50cbc8b28466857be256079aa9406407f48 Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Fri, 4 Jul 2014 19:14:04 -0500 Subject: [PATCH 016/145] Adding Interfaces to families of operators; permits much cleaner UDFs * New interface IOperatorAWithB for (geom, geom) -> geom * Applied it to Intersection, Difference, SymmetricDifference, and Union More to come. --- .../esri/core/geometry/IOperatorAWithB.java | 45 +++++++++++++++++++ .../core/geometry/OperatorDifference.java | 3 +- .../core/geometry/OperatorIntersection.java | 3 +- .../geometry/OperatorSymmetricDifference.java | 3 +- .../com/esri/core/geometry/OperatorUnion.java | 3 +- 5 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/IOperatorAWithB.java diff --git a/src/main/java/com/esri/core/geometry/IOperatorAWithB.java b/src/main/java/com/esri/core/geometry/IOperatorAWithB.java new file mode 100644 index 00000000..b01b7573 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/IOperatorAWithB.java @@ -0,0 +1,45 @@ +/* + Copyright 1995-2013 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; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.ProgressTracker; + +/** + * Interface for operators that act on two geometries to produce a new geometry as result. + */ +public interface IOperatorAWithB { + + /** + * 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. + * + */ + public Geometry execute(Geometry geom1, Geometry geom2, + SpatialReference sr, ProgressTracker progressTracker); + +} diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 1dbfc9ec..15e2845f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -25,11 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** * Difference of geometries. */ -public abstract class OperatorDifference extends Operator { +public abstract class OperatorDifference extends Operator implements IOperatorAWithB { @Override public Type getType() { diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index f69c86ec..ab3ae72f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -25,11 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** *Intersection of geometries by a given geometry. */ -public abstract class OperatorIntersection extends Operator { +public abstract class OperatorIntersection extends Operator implements IOperatorAWithB { @Override public Type getType() { return Type.Intersection; diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index b21f8745..b3a5bd28 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -24,12 +24,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** * Symmetric difference (XOR) operation between geometries. * */ -public abstract class OperatorSymmetricDifference extends Operator { +public abstract class OperatorSymmetricDifference extends Operator implements IOperatorAWithB { @Override public Type getType() { return Type.Difference; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index c7cf49b4..b66f4403 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -25,13 +25,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** * * Union of geometries. * */ -public abstract class OperatorUnion extends Operator { +public abstract class OperatorUnion extends Operator implements IOperatorAWithB { @Override public Type getType() { return Type.Union; From f1b1db07e699e41d666b010ee7465e88d871a0f1 Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Fri, 4 Jul 2014 19:15:36 -0500 Subject: [PATCH 017/145] Minor fixes: error messages on import; gitignore artifacts; make NumberUtils available to UDFs --- .gitignore | 1 + .../java/com/esri/core/geometry/NumberUtils.java | 6 +++--- .../core/geometry/OperatorExportToWktLocal.java | 16 ++++++++-------- .../java/com/esri/core/geometry/WktParser.java | 4 +++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 03c080fb..85898c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ libs/ Icon? ehthumbs.db Thumbs.db +target/* diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 7e540f0d..383aa7af 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -24,13 +24,13 @@ package com.esri.core.geometry; -class NumberUtils { +public class NumberUtils { - static int snap(int v, int minv, int maxv) { + public static int snap(int v, int minv, int maxv) { return v < minv ? minv : v > maxv ? maxv : v; } - static long snap(long v, long minv, long maxv) { + public static long snap(long v, long minv, long maxv) { return v < minv ? minv : v > maxv ? maxv : v; } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index cdcefd84..a48b1f5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -42,7 +42,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Polygon as Line/Point : "+export_flags); exportPolygonToWkt(export_flags, (Polygon) geometry, string); return; @@ -52,7 +52,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Polyline as (Multi)Polygon/(Multi)Point : "+export_flags); exportPolylineToWkt(export_flags, (Polyline) geometry, string); return; @@ -62,7 +62,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPolygon) != 0 || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a MultiPoint as (Multi)LineString/(Multi)Polygon: "+export_flags); exportMultiPointToWkt(export_flags, (MultiPoint) geometry, string); return; @@ -72,7 +72,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPolygon) != 0 || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Point as (Multi)LineString/(Multi)Polygon: "+export_flags); exportPointToWkt(export_flags, (Point) geometry, string); return; @@ -82,7 +82,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export an Envelop as (Multi)LineString/(Multi)Point: "+export_flags); exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); return; @@ -149,7 +149,7 @@ static void exportPolygonToWkt(int export_flags, Polygon polygon, if ((export_flags & WktExportFlags.wktExportPolygon) != 0) { if (polygon_count > 1) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Polygon with specified export flags: "+export_flags); polygonTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, path_count, string); @@ -207,7 +207,7 @@ static void exportPolylineToWkt(int export_flags, Polyline polyline, if ((export_flags & WktExportFlags.wktExportLineString) != 0) { if (path_count > 1) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a LineString with specified export flags: "+export_flags); lineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, string); @@ -256,7 +256,7 @@ static void exportMultiPointToWkt(int export_flags, MultiPoint multipoint, if ((export_flags & WktExportFlags.wktExportPoint) != 0) { if (point_count > 1) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Point with specified export flags: "+export_flags); pointTaggedTextFromMultiPoint_(precision, b_export_zs, b_export_ms, zs, ms, position, string); diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index 91f0198d..02fbc058 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -264,7 +264,9 @@ private void geometry_() { m_current_token_type = WktToken.geometrycollection; m_function_stack.add(State.geometryCollectionStart); } else { - throw new IllegalArgumentException(); + String snippet = (m_wkt_string.length() > 200 ? + m_wkt_string.substring(0,200)+"..." : m_wkt_string); + throw new IllegalArgumentException("Could not parse Well-Known Text: "+snippet); } m_function_stack.add(State.attributes); From 36cc1ee81037c04b85efe90dee52d17f6c0157db Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Tue, 8 Jul 2014 04:04:58 -0500 Subject: [PATCH 018/145] toString() on Geometry and OGCGeometry --- src/main/java/com/esri/core/geometry/Geometry.java | 8 ++++++++ src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 0257579c..fbbee9f3 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -26,6 +26,8 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.WktExportFlags; import java.io.ObjectStreamException; import java.io.Serializable; @@ -507,6 +509,12 @@ static Geometry _clone(Geometry src) { return geom; } + public String toString() { + String snippet = GeometryEngine.geometryToWkt(this, WktExportFlags.wktExportDefaults); + if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } + return String.format("%s: %s", this.getClass().getSimpleName(), snippet); + } + /** * The stateFlag value changes with changes applied to this geometry. This * allows the user to keep track of the geometry's state. 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 ef6e1452..1a6e8b09 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -84,6 +84,12 @@ public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); } + public String toString() { + String snippet = asText(); + if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } + return String.format("%s: %s", this.getClass().getSimpleName(), snippet); + } + public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToWkb); From 6ab844c1983864dd10fd039ffab20bbe4b2b15fd Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Tue, 8 Jul 2014 04:10:27 -0500 Subject: [PATCH 019/145] CombineOperator replaces IOperatorAWithB.java --- .../geometry/{IOperatorAWithB.java => CombineOperator.java} | 2 +- src/main/java/com/esri/core/geometry/OperatorDifference.java | 4 ++-- .../java/com/esri/core/geometry/OperatorIntersection.java | 4 ++-- .../com/esri/core/geometry/OperatorSymmetricDifference.java | 4 ++-- src/main/java/com/esri/core/geometry/OperatorUnion.java | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/com/esri/core/geometry/{IOperatorAWithB.java => CombineOperator.java} (97%) diff --git a/src/main/java/com/esri/core/geometry/IOperatorAWithB.java b/src/main/java/com/esri/core/geometry/CombineOperator.java similarity index 97% rename from src/main/java/com/esri/core/geometry/IOperatorAWithB.java rename to src/main/java/com/esri/core/geometry/CombineOperator.java index b01b7573..e929ca39 100644 --- a/src/main/java/com/esri/core/geometry/IOperatorAWithB.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -30,7 +30,7 @@ /** * Interface for operators that act on two geometries to produce a new geometry as result. */ -public interface IOperatorAWithB { +public interface CombineOperator { /** * Operation on two geometries, returning a third. Examples include diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 15e2845f..607285a6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -25,12 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** * Difference of geometries. */ -public abstract class OperatorDifference extends Operator implements IOperatorAWithB { +public abstract class OperatorDifference extends Operator implements CombineOperator { @Override public Type getType() { diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index ab3ae72f..2416fb06 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -25,12 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** *Intersection of geometries by a given geometry. */ -public abstract class OperatorIntersection extends Operator implements IOperatorAWithB { +public abstract class OperatorIntersection extends Operator implements CombineOperator { @Override public Type getType() { return Type.Intersection; diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index b3a5bd28..17a2f5bf 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -24,13 +24,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** * Symmetric difference (XOR) operation between geometries. * */ -public abstract class OperatorSymmetricDifference extends Operator implements IOperatorAWithB { +public abstract class OperatorSymmetricDifference extends Operator implements CombineOperator { @Override public Type getType() { return Type.Difference; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index b66f4403..95cd8f93 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -25,14 +25,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** * * Union of geometries. * */ -public abstract class OperatorUnion extends Operator implements IOperatorAWithB { +public abstract class OperatorUnion extends Operator implements CombineOperator { @Override public Type getType() { return Type.Union; From c885e7955c415861fa4d1ad064e2eb27fc2b5237 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 23 Jul 2014 17:30:46 -0700 Subject: [PATCH 020/145] Fixes for issues 52, 51, 48, 46, 45, 41, 40, 36 --- .../core/geometry/AttributeStreamOfDbl.java | 6 +- .../core/geometry/AttributeStreamOfInt32.java | 4 + .../java/com/esri/core/geometry/Boundary.java | 81 +- .../java/com/esri/core/geometry/Bufferer.java | 8 +- .../java/com/esri/core/geometry/Clipper.java | 4 +- .../com/esri/core/geometry/Clusterer.java | 610 +++++----- .../esri/core/geometry/CombineOperator.java | 45 +- .../esri/core/geometry/ConstructOffset.java | 6 +- .../esri/core/geometry/CrackAndCluster.java | 119 +- .../java/com/esri/core/geometry/Cracker.java | 71 +- .../java/com/esri/core/geometry/Cutter.java | 8 +- .../com/esri/core/geometry/EditShape.java | 60 +- .../java/com/esri/core/geometry/Envelope.java | 13 +- .../com/esri/core/geometry/Envelope1D.java | 26 + .../com/esri/core/geometry/Envelope2D.java | 68 +- .../geometry/Envelope2DIntersectorImpl.java | 66 +- .../com/esri/core/geometry/Envelope3D.java | 4 +- .../esri/core/geometry/GeodeticCurveType.java | 5 + .../java/com/esri/core/geometry/Geometry.java | 35 +- .../core/geometry/GeometryAccelerators.java | 42 + .../esri/core/geometry/GeometryEngine.java | 52 +- .../esri/core/geometry/GeometryException.java | 3 + .../esri/core/geometry/IndexHashTable.java | 111 +- .../com/esri/core/geometry/InternalUtils.java | 110 +- .../esri/core/geometry/IntervalTreeImpl.java | 2 +- .../esri/core/geometry/JsonStringWriter.java | 2 +- .../java/com/esri/core/geometry/Line.java | 36 +- .../com/esri/core/geometry/MapGeometry.java | 57 + .../com/esri/core/geometry/MathUtils.java | 7 + .../com/esri/core/geometry/MultiPath.java | 52 +- .../com/esri/core/geometry/MultiPathImpl.java | 71 +- .../com/esri/core/geometry/MultiPoint.java | 5 + .../esri/core/geometry/MultiPointImpl.java | 5 + .../geometry/MultiVertexGeometryImpl.java | 7 +- .../com/esri/core/geometry/NumberUtils.java | 48 +- .../java/com/esri/core/geometry/Operator.java | 2 +- .../esri/core/geometry/OperatorCutCursor.java | 182 +-- .../OperatorDensifyByLengthCursor.java | 2 +- .../core/geometry/OperatorDifference.java | 1 - .../OperatorExportToESRIShapeCursor.java | 2 +- .../geometry/OperatorExportToWkbLocal.java | 2 +- .../geometry/OperatorExportToWktLocal.java | 6 +- .../core/geometry/OperatorFactoryLocal.java | 20 +- .../geometry/OperatorGeneralizeCursor.java | 2 +- .../core/geometry/OperatorGeodesicBuffer.java | 67 ++ .../geometry/OperatorGeodesicBufferLocal.java | 44 + .../OperatorGeodeticDensifyByLength.java | 60 + .../OperatorGeodeticDensifyLocal.java | 43 + .../core/geometry/OperatorGeodeticLength.java | 17 - .../geometry/OperatorGeodeticLengthLocal.java | 6 - .../OperatorImportFromGeoJsonLocal.java | 26 +- .../geometry/OperatorImportFromWkbLocal.java | 18 +- .../geometry/OperatorImportFromWktLocal.java | 19 +- .../OperatorInternalRelationUtils.java | 14 +- .../core/geometry/OperatorIntersection.java | 4 +- .../geometry/OperatorIntersectionCursor.java | 24 +- .../geometry/OperatorIntersectionLocal.java | 3 +- .../esri/core/geometry/OperatorProject.java | 32 +- .../core/geometry/OperatorProjectLocal.java | 11 + .../core/geometry/OperatorProximity2D.java | 26 +- .../geometry/OperatorProximity2DLocal.java | 276 ++++- .../esri/core/geometry/OperatorRelate.java | 15 + .../OperatorShapePreservingDensify.java | 71 ++ .../OperatorShapePreservingDensifyLocal.java | 44 + .../core/geometry/OperatorSimpleRelation.java | 17 +- .../geometry/OperatorSimplifyLocalHelper.java | 50 +- .../geometry/OperatorSimplifyLocalOGC.java | 2 - .../geometry/OperatorSymmetricDifference.java | 1 - .../com/esri/core/geometry/OperatorUnion.java | 3 +- .../core/geometry/OperatorUnionCursor.java | 497 ++++---- .../geometry/PlaneSweepCrackerHelper.java | 238 +--- .../java/com/esri/core/geometry/Point.java | 25 +- .../java/com/esri/core/geometry/Point2D.java | 26 + .../java/com/esri/core/geometry/Point3D.java | 8 +- .../java/com/esri/core/geometry/Polygon.java | 4 +- .../com/esri/core/geometry/PolygonUtils.java | 29 +- .../java/com/esri/core/geometry/Polyline.java | 6 +- .../esri/core/geometry/ProgressTracker.java | 9 + .../esri/core/geometry/Proximity2DResult.java | 29 + .../com/esri/core/geometry/QuadTreeImpl.java | 157 +-- .../core/geometry/RasterizedGeometry2D.java | 2 +- .../geometry/RasterizedGeometry2DImpl.java | 4 +- .../core/geometry/RelationalOperations.java | 1061 ++++++++++------- .../geometry/RelationalOperationsMatrix.java | 725 ++++++++--- .../core/geometry/RingOrientationFixer.java | 160 ++- .../java/com/esri/core/geometry/Segment.java | 27 +- .../com/esri/core/geometry/SegmentBuffer.java | 10 +- .../core/geometry/SegmentIntersector.java | 12 +- .../esri/core/geometry/SegmentIterator.java | 12 + .../core/geometry/SegmentIteratorImpl.java | 42 +- .../com/esri/core/geometry/Simplificator.java | 13 +- .../core/geometry/SpatialReferenceImpl.java | 8 +- .../geometry/StridedIndexTypeCollection.java | 118 +- .../com/esri/core/geometry/TopoGraph.java | 576 +++++---- .../core/geometry/TopologicalOperations.java | 342 +++--- .../java/com/esri/core/geometry/Treap.java | 16 +- .../com/esri/core/geometry/WktParser.java | 9 +- .../ogc/OGCConcreteGeometryCollection.java | 22 +- .../esri/core/geometry/ogc/OGCGeometry.java | 91 +- .../esri/core/geometry/ogc/OGCPolygon.java | 2 +- .../com/esri/core/geometry/GeometryUtils.java | 28 - .../esri/core/geometry/TestAttributes.java | 3 +- .../geometry/TestEnvelope2DIntersector.java | 14 +- .../com/esri/core/geometry/TestFailed.java | 1 - .../com/esri/core/geometry/TestGeodetic.java | 2 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 1 - .../esri/core/geometry/TestImportExport.java | 4 +- .../esri/core/geometry/TestIntersect2.java | 1 - .../esri/core/geometry/TestIntersection.java | 36 + .../esri/core/geometry/TestJsonParser.java | 8 +- .../java/com/esri/core/geometry/TestOGC.java | 108 +- .../com/esri/core/geometry/TestOffset.java | 2 - .../com/esri/core/geometry/TestPoint.java | 57 +- .../esri/core/geometry/TestProximity2D.java | 29 +- .../com/esri/core/geometry/TestRelation.java | 905 +++++++++----- .../core/geometry/TestShapePreserving.java | 151 --- .../com/esri/core/geometry/TestSimplify.java | 4 + .../com/esri/core/geometry/TestUnion.java | 2 - .../esri/core/geometry/TestWKBSupport.java | 2 - .../java/com/esri/core/geometry/Utils.java | 4 +- 120 files changed, 5412 insertions(+), 3191 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java delete mode 100644 src/test/java/com/esri/core/geometry/TestShapePreserving.java diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index ec56a58b..09f87ff4 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -571,8 +571,10 @@ public void eraseRange(int index, int count, int validSize) { if (index + count > m_size) throw new GeometryException("invalid_call"); - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); + if (validSize - (index + count) > 0) { + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + } m_size -= count; } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 0f7488ec..609a19fd 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -303,6 +303,10 @@ public int getLast() { return m_buffer[m_size - 1]; } + public void setLast(int v) { + m_buffer[m_size - 1] = v; + } + public void removeLast() { resize(m_size - 1); } diff --git a/src/main/java/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java index f9b99f9a..13958cb7 100644 --- a/src/main/java/com/esri/core/geometry/Boundary.java +++ b/src/main/java/com/esri/core/geometry/Boundary.java @@ -24,17 +24,52 @@ package com.esri.core.geometry; class Boundary { + + static boolean hasNonEmptyBoundary(Geometry geom, + ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return false; + + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.Polygon) { + if (geom.calculateArea2D() == 0) + return false; + + return true; + } else if (gt == Geometry.Type.Polyline) { + boolean[] b = new boolean[1]; + b[0] = false; + calculatePolylineBoundary_(geom._getImpl(), progress_tracker, true, + b); + return b[0]; + } else if (gt == Geometry.Type.Envelope) { + return true; + } else if (Geometry.isSegment(gt.value())) { + if (!((Segment) geom).isClosed()) { + return true; + } + + return false; + } else if (Geometry.isPoint(gt.value())) { + return false; + } + + return false; + } + static Geometry calculate(Geometry geom, ProgressTracker progress_tracker) { int gt = geom.getType().value(); if (gt == Geometry.GeometryType.Polygon) { Polyline dst = new Polyline(geom.getDescription()); - if (!geom.isEmpty()) { - ((MultiPathImpl)geom._getImpl())._copyToUnsafe((MultiPathImpl)dst._getImpl()); + if (!geom.isEmpty()) { + ((MultiPathImpl) geom._getImpl()) + ._copyToUnsafe((MultiPathImpl) dst._getImpl()); } return dst; } else if (gt == Geometry.GeometryType.Polyline) { - return calculatePolylineBoundary_(geom._getImpl(), progress_tracker); + return calculatePolylineBoundary_(geom._getImpl(), + progress_tracker, false, null); } else if (gt == Geometry.GeometryType.Envelope) { Polyline dst = new Polyline(geom.getDescription()); if (!geom.isEmpty()) @@ -98,9 +133,15 @@ public double getValue(int index) { } static MultiPoint calculatePolylineBoundary_(Object impl, - ProgressTracker progress_tracker) { + ProgressTracker progress_tracker, + boolean only_check_non_empty_boundary, boolean[] not_empty) { + if (not_empty != null) + not_empty[0] = false; MultiPathImpl mpImpl = (MultiPathImpl) impl; - MultiPoint dst = new MultiPoint(mpImpl.getDescription()); + MultiPoint dst = null; + if (!only_check_non_empty_boundary) + dst = new MultiPoint(mpImpl.getDescription()); + if (!mpImpl.isEmpty()) { AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); indices.reserve(mpImpl.getPathCount() * 2); @@ -151,6 +192,12 @@ static MultiPoint calculatePolylineBoundary_(Object impl, } else { if ((counter & 1) == 0) {// remove boundary point indices.set(ind, NumberUtils.intMax()); + } else { + if (only_check_non_empty_boundary) { + if (not_empty != null) + not_empty[0] = true; + return null; + } } ptPrev.setCoords(pt); @@ -161,20 +208,30 @@ static MultiPoint calculatePolylineBoundary_(Object impl, if ((counter & 1) == 0) {// remove the point indices.set(ind, NumberUtils.intMax()); + } else { + if (only_check_non_empty_boundary) { + if (not_empty != null) + not_empty[0] = true; + return null; + } } + if (!only_check_non_empty_boundary) { + indices.sort(0, indices.size()); - indices.sort(0, indices.size()); - - for (int i = 0, n = indices.size(); i < n; i++) { - if (indices.get(i) == NumberUtils.intMax()) - break; + for (int i = 0, n = indices.size(); i < n; i++) { + if (indices.get(i) == NumberUtils.intMax()) + break; - mpImpl.getPointByVal(indices.get(i), point); - dst.add(point); + mpImpl.getPointByVal(indices.get(i), point); + dst.add(point); + } } } } + if (only_check_non_empty_boundary) + return null; + return dst; } } diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index db4318ad..a8517844 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -470,7 +470,7 @@ private Geometry buffer_() { case Geometry.GeometryType.Envelope: return bufferEnvelope_(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -796,7 +796,7 @@ private Polygon bufferConvexPath_(MultiPath src, int ipath) { src_mp.getXY(path_start + (i + 2) % path_size, pt_3); v_1.sub(pt_2, pt_1); if (v_1.length() == 0) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); v_1.leftPerpendicular(); v_1.normalize(); @@ -813,7 +813,7 @@ private Polygon bufferConvexPath_(MultiPath src, int ipath) { v_2.sub(pt_3, pt_2); if (v_2.length() == 0) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); v_2.leftPerpendicular(); v_2.normalize(); @@ -1085,7 +1085,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, pt_current, BufferCommand.Flags.enum_arc, m_buffer_commands.size() + 1, m_buffer_commands.size() - 1)); - } else if (!pt_left_prev.equals(pt)) { + } else if (!pt_left_prev.isEqual(pt)) { m_buffer_commands.add(new BufferCommand(pt_left_prev, pt_current, m_buffer_commands.size() + 1, m_buffer_commands.size() - 1, "dummy")); diff --git a/src/main/java/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java index fd064c95..2af2e0a2 100644 --- a/src/main/java/com/esri/core/geometry/Clipper.java +++ b/src/main/java/com/esri/core/geometry/Clipper.java @@ -1180,7 +1180,7 @@ static Geometry clip(Geometry geometry, Envelope2D extent, .queryEnvelopeInGeometry(extent); if (hit == RasterizedGeometry2D.HitType.Inside) { if (geomtype != Geometry.Type.Polygon.value()) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); Polygon poly = new Polygon(geometry.getDescription()); poly.addEnvelope(extent, false); @@ -1242,7 +1242,7 @@ static Geometry clip(Geometry geometry, Envelope2D extent, densify_dist); default: assert (false); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java index 9fb3cb46..8ff5efd8 100644 --- a/src/main/java/com/esri/core/geometry/Clusterer.java +++ b/src/main/java/com/esri/core/geometry/Clusterer.java @@ -33,13 +33,14 @@ final class Clusterer { // (clustered). // Uses reciprocal clustering (cluster vertices that are mutual nearest // neighbours) - static boolean executeReciprocal(EditShape shape, double tolerance) { - Clusterer clusterer = new Clusterer(); - clusterer.m_shape = shape; - clusterer.m_tolerance = tolerance; - clusterer.m_cell_size = 2 * tolerance; - return clusterer.clusterReciprocal_(); - } + /* + * static boolean executeReciprocal(EditShape shape, double tolerance) { + * Clusterer clusterer = new Clusterer(); clusterer.m_shape = shape; + * clusterer.m_tolerance = tolerance; clusterer.m_sqr_tolerance = tolerance + * * tolerance; clusterer.m_cell_size = 2 * tolerance; + * clusterer.m_inv_cell_size = 1.0 / clusterer.m_cell_size; return + * clusterer.clusterReciprocal_(); } + */ // Clusters vertices of the shape. Returns True, if some vertices were moved // (clustered). @@ -49,88 +50,102 @@ static boolean executeNonReciprocal(EditShape shape, double tolerance) { Clusterer clusterer = new Clusterer(); clusterer.m_shape = shape; clusterer.m_tolerance = tolerance; - clusterer.m_cell_size = 2 * tolerance;// revisit this value. Probably - // should be m_tolerance? + clusterer.m_sqr_tolerance = tolerance * tolerance; + clusterer.m_cell_size = 2 * tolerance; + clusterer.m_inv_cell_size = 1.0 / clusterer.m_cell_size; return clusterer.clusterNonReciprocal_(); } // Use b_conservative == True for simplify, and False for IsSimple. This // makes sure Simplified shape is more robust to transformations. - static boolean isClusterCandidate(double x_1, double y1, double x2, - double y2, double tolerance) { + static boolean isClusterCandidate_(double x_1, double y1, double x2, + double y2, double sqr_tolerance) { double dx = x_1 - x2; double dy = y1 - y2; - return Math.sqrt(dx * dx + dy * dy) <= tolerance; + return dx * dx + dy * dy <= sqr_tolerance; } Point2D m_origin = new Point2D(); double m_tolerance; + double m_sqr_tolerance; double m_cell_size; + double m_inv_cell_size; int[] m_bucket_array = new int[4];// temporary 4 element array - int m_dbg_candidate_check_count; - int m_hash_values; + int[] m_bucket_hash = new int[4];// temporary 4 element array + int m_dbg_candidate_check_count = 0; + int m_hash_values = -1; + int m_new_clusters = -1; - static int hashFunction_(double xi, double yi) { + static int hashFunction_(int xi, int yi) { int h = NumberUtils.hash(xi); return NumberUtils.hash(h, yi); } - class ClusterHashFunction extends IndexHashTable.HashFunction { - IndexMultiList m_clusters; + final class ClusterHashFunction extends IndexHashTable.HashFunction { EditShape m_shape; - double m_tolerance; - double m_cell_size; + double m_sqr_tolerance; + double m_inv_cell_size; Point2D m_origin = new Point2D(); Point2D m_pt = new Point2D(); Point2D m_pt_2 = new Point2D(); int m_hash_values; - public ClusterHashFunction(IndexMultiList clusters, EditShape shape, - Point2D origin, double tolerance, double cell_size, - int hash_values) { - m_clusters = clusters; + public ClusterHashFunction(EditShape shape, Point2D origin, + double sqr_tolerance, double inv_cell_size, int hash_values) { m_shape = shape; - m_tolerance = tolerance; - m_cell_size = cell_size; + m_sqr_tolerance = sqr_tolerance; + m_inv_cell_size = inv_cell_size; m_origin = origin; m_hash_values = hash_values; m_pt.setNaN(); m_pt_2.setNaN(); } - @Override - public int getHash(int element) { - int vertex = m_clusters.getFirstElement(element); - return m_shape.getUserIndex(vertex, m_hash_values); + int calculate_hash(int element) { + return calculate_hash_from_vertex(element); } - int calculateHash(int element) { - int vertex = m_clusters.getFirstElement(element); + int dbg_calculate_hash_from_xy(double x, double y) { + double dx = x - m_origin.x; + int xi = (int) (dx * m_inv_cell_size + 0.5); + double dy = y - m_origin.y; + int yi = (int) (dy * m_inv_cell_size + 0.5); + return hashFunction_(xi, yi); + } + + int calculate_hash_from_vertex(int vertex) { m_shape.getXY(vertex, m_pt); double dx = m_pt.x - m_origin.x; - double xi = Math.round(dx / m_cell_size); + int xi = (int) (dx * m_inv_cell_size + 0.5); double dy = m_pt.y - m_origin.y; - double yi = Math.round(dy / m_cell_size); + int yi = (int) (dy * m_inv_cell_size + 0.5); return hashFunction_(xi, yi); } + @Override + public int getHash(int element) { + return m_shape.getUserIndex(element, m_hash_values); + } + @Override public boolean equal(int element_1, int element_2) { - int xyindex_1 = m_clusters.getFirstElement(element_1); + int xyindex_1 = element_1; + int xyindex_2 = element_2; m_shape.getXY(xyindex_1, m_pt); - int xyindex_2 = m_clusters.getFirstElement(element_2); m_shape.getXY(xyindex_2, m_pt_2); - return isClusterCandidate(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, - m_tolerance); + return isClusterCandidate_(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, + m_sqr_tolerance); } @Override public int getHash(Object element_descriptor) { + // UNUSED return 0; } @Override public boolean equal(Object element_descriptor, int element) { + // UNUSED return false; } }; @@ -141,30 +156,30 @@ public boolean equal(Object element_descriptor, int element) { IndexHashTable m_hash_table; static class ClusterCandidate { - public int cluster; + public int vertex; double distance; }; void getNearestNeighbourCandidate_(int xyindex, Point2D pointOfInterest, int bucket_ptr, ClusterCandidate candidate) { - candidate.cluster = IndexMultiList.nullNode(); + candidate.vertex = -1; candidate.distance = NumberUtils.doubleMax(); Point2D pt = new Point2D(); - for (int node = bucket_ptr; node != IndexHashTable.nullNode(); node = m_hash_table + for (int node = bucket_ptr; node != -1; node = m_hash_table .getNextInBucket(node)) { - int cluster = m_hash_table.getElement(node); - int xyind = m_clusters.getFirstElement(cluster); + int xyind = m_hash_table.getElement(node); if (xyindex == xyind) continue; + m_shape.getXY(xyind, pt); - if (isClusterCandidate(pointOfInterest.x, pointOfInterest.y, pt.x, - pt.y, m_tolerance)) { + if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_sqr_tolerance)) { pt.sub(pointOfInterest); double l = pt.length(); if (l < candidate.distance) { candidate.distance = l; - candidate.cluster = cluster; + candidate.vertex = xyind; } } } @@ -174,26 +189,26 @@ void findClusterCandidate_(int xyindex, ClusterCandidate candidate) { Point2D pointOfInterest = new Point2D(); m_shape.getXY(xyindex, pointOfInterest); double x_0 = pointOfInterest.x - m_origin.x; - double x = x_0 / m_cell_size; + double x = x_0 * m_inv_cell_size; double y0 = pointOfInterest.y - m_origin.y; - double y = y0 / m_cell_size; + double y = y0 * m_inv_cell_size; - double xi = Math.round(x - 0.5); - double yi = Math.round(y - 0.5); + int xi = (int) x; + int yi = (int) y; // find the nearest neighbour in the 4 neigbouring cells. - candidate.cluster = IndexHashTable.nullNode(); + candidate.vertex = -1; candidate.distance = NumberUtils.doubleMax(); ClusterCandidate c = new ClusterCandidate(); - for (double dx = 0; dx <= 1.0; dx += 1.0) { - for (double dy = 0; dy <= 1.0; dy += 1.0) { + for (int dx = 0; dx <= 1; dx += 1) { + for (int dy = 0; dy <= 1; dy += 1) { int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi + dx, yi + dy)); if (bucket_ptr != IndexHashTable.nullNode()) { getNearestNeighbourCandidate_(xyindex, pointOfInterest, bucket_ptr, c); - if (c.cluster != IndexHashTable.nullNode() + if (c.vertex != IndexHashTable.nullNode() && c.distance < candidate.distance) { candidate = c; } @@ -207,81 +222,192 @@ void collectClusterCandidates_(int xyindex, Point2D pointOfInterest = new Point2D(); m_shape.getXY(xyindex, pointOfInterest); double x_0 = pointOfInterest.x - m_origin.x; - double x = x_0 / m_cell_size; + double x = x_0 * m_inv_cell_size; double y0 = pointOfInterest.y - m_origin.y; - double y = y0 / m_cell_size; + double y = y0 * m_inv_cell_size; + + int xi = (int) x; + int yi = (int) y; - double xi = Math.round(x - 0.5); - double yi = Math.round(y - 0.5); - for (int i = 0; i < 4; i++) - m_bucket_array[i] = -1; int bucket_count = 0; // find all nearest neighbours in the 4 neigbouring cells. // Note, because we check four neighbours, there should be 4 times more // bins in the hash table to reduce collision probability in this loop. - for (double dx = 0; dx <= 1.0; dx += 1.0) { - for (double dy = 0; dy <= 1.0; dy += 1.0) { - int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi - + dx, yi + dy)); - if (bucket_ptr != IndexHashTable.nullNode()) { + for (int dx = 0; dx <= 1; dx += 1) { + for (int dy = 0; dy <= 1; dy += 1) { + int hash = hashFunction_(xi + dx, yi + dy); + int bucket_ptr = m_hash_table.getFirstInBucket(hash); + if (bucket_ptr != -1) { // Check if we already have this bucket. // There could be a hash collision for neighbouring buckets. - for (int j = 0; j < bucket_count; j++) { - if (m_bucket_array[j] == bucket_ptr) { - bucket_ptr = -1;// hash values for two neighbouring - // cells have collided. - break; - } - } + m_bucket_array[bucket_count] = bucket_ptr; + m_bucket_hash[bucket_count] = hash; - if (bucket_ptr != -1) { - m_bucket_array[bucket_count] = bucket_ptr; - bucket_count++; - } + bucket_count++; } } } - for (int i = 0; i < 4; i++) { - int bucket_ptr = m_bucket_array[i]; - if (bucket_ptr == -1) - break; + // Clear duplicate buckets + // There could be a hash collision for neighboring buckets. + for (int j = bucket_count - 1; j >= 1; j--) { + int bucket_ptr = m_bucket_array[j]; + for (int i = j - 1; i >= 0; i--) { + if (bucket_ptr == m_bucket_array[i])// hash values for two + // neighbouring cells have + // collided. + { + m_bucket_hash[i] = -1; // forget collided hash + bucket_count--; + if (j != bucket_count) { + m_bucket_hash[j] = m_bucket_hash[bucket_count]; + m_bucket_array[j] = m_bucket_array[bucket_count]; + } + break;// duplicate + } + } + } - collectNearestNeighbourCandidates_(xyindex, pointOfInterest, - bucket_ptr, candidates); + for (int i = 0; i < bucket_count; i++) { + collectNearestNeighbourCandidates_(xyindex, m_bucket_hash[i], + pointOfInterest, m_bucket_array[i], candidates); } } - void collectNearestNeighbourCandidates_(int xyindex, + void collectNearestNeighbourCandidates_(int xyindex, int hash, Point2D pointOfInterest, int bucket_ptr, AttributeStreamOfInt32 candidates) { Point2D pt = new Point2D(); - for (int node = bucket_ptr; node != IndexHashTable.nullNode(); node = m_hash_table + for (int node = bucket_ptr; node != -1; node = m_hash_table .getNextInBucket(node)) { - int cluster = m_hash_table.getElement(node); - int xyind = m_clusters.getFirstElement(cluster); - if (xyindex == xyind) - continue; + int xyind = m_hash_table.getElement(node); + if (xyindex == xyind || hash != -1 + && m_shape.getUserIndex(xyind, m_hash_values) != hash) + continue;// processing same vertex, or the bucket hash modulo + // bin count collides. + m_shape.getXY(xyind, pt); m_dbg_candidate_check_count++; - if (isClusterCandidate(pointOfInterest.x, pointOfInterest.y, pt.x, - pt.y, m_tolerance)) { + if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_sqr_tolerance)) { candidates.add(node);// note that we add the cluster node // instead of the cluster. } } } - boolean mergeClusters_(int cluster_1, int cluster_2) { - int xyindex_1 = m_clusters.getFirstElement(cluster_1); - int xyindex_2 = m_clusters.getFirstElement(cluster_2); - boolean res = mergeVertices_(xyindex_1, xyindex_2); - m_clusters.concatenateLists(cluster_1, cluster_2); - int hash = m_hash_function.calculateHash(cluster_1); - m_shape.setUserIndex(xyindex_1, m_hash_values, hash); + boolean mergeClusters_(int vertex1, int vertex2, boolean update_hash) { + int cluster_1 = m_shape.getUserIndex(vertex1, m_new_clusters); + int cluster_2 = m_shape.getUserIndex(vertex2, m_new_clusters); + assert (cluster_1 != StridedIndexTypeCollection.impossibleIndex2()); + assert (cluster_2 != StridedIndexTypeCollection.impossibleIndex2()); + + if (cluster_1 == -1) { + cluster_1 = m_clusters.createList(); + m_clusters.addElement(cluster_1, vertex1); + m_shape.setUserIndex(vertex1, m_new_clusters, cluster_1); + } + + if (cluster_2 == -1) { + m_clusters.addElement(cluster_1, vertex2); + } else { + m_clusters.concatenateLists(cluster_1, cluster_2); + } + + // ensure only single vertex refers to the cluster. + m_shape.setUserIndex(vertex2, m_new_clusters, + StridedIndexTypeCollection.impossibleIndex2()); + + // merge cordinates + boolean res = mergeVertices_(vertex1, vertex2); + + if (update_hash) { + int hash = m_hash_function.calculate_hash_from_vertex(vertex1); + m_shape.setUserIndex(vertex1, m_hash_values, hash); + } else { + + } + + return res; + } + + // recalculate coordinates of the vertices by averaging them using weights. + // return true if the coordinates has changed. + static boolean mergeVertices(Point pt_1, Point pt_2, double w_1, + int rank_1, double w_2, int rank_2, Point pt_res, double[] w_res, + int[] rank_res) { + assert (!pt_1.isEmpty() && !pt_2.isEmpty()); + boolean res = pt_1.equals(pt_2); + + if (rank_1 > rank_2) { + pt_res = pt_1; + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + return res; + } else if (rank_2 > rank_1) { + pt_res = pt_2; + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + return res; + } + + pt_res = pt_1; + Point2D pt2d = new Point2D(); + mergeVertices2D(pt_1.getXY(), pt_2.getXY(), w_1, rank_1, w_2, rank_2, + pt2d, w_res, rank_res); + pt_res.setXY(pt2d); return res; } + static boolean mergeVertices2D(Point2D pt_1, Point2D pt_2, double w_1, + int rank_1, double w_2, int rank_2, Point2D pt_res, double[] w_res, + int[] rank_res) { + double w = w_1 + w_2; + boolean r = false; + double x = pt_1.x; + if (pt_1.x != pt_2.x) { + if (rank_1 == rank_2) + x = (pt_1.x * w_1 + pt_2.x * w_2) / w; + + r = true; + } + double y = pt_1.y; + if (pt_1.y != pt_2.y) { + if (rank_1 == rank_2) + y = (pt_1.y * w_1 + pt_2.y * w_2) / w; + + r = true; + } + + if (rank_1 != rank_2) { + if (rank_1 > rank_2) { + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + pt_res = pt_1; + } else { + if (w_res != null) { + rank_res[0] = rank_2; + w_res[0] = w_2; + } + pt_res = pt_2; + } + } else { + pt_res.setCoords(x, y); + if (w_res != null) { + w_res[0] = w; + rank_res[0] = rank_1; + } + } + + return r; + } + boolean mergeVertices_(int vert_1, int vert_2) { Point2D pt_1 = new Point2D(); m_shape.getXY(vert_1, pt_1); @@ -305,197 +431,39 @@ boolean mergeVertices_(int vert_1, int vert_2) { if (r > 0) m_shape.setXY(vert_1, x, y); + m_shape.setWeight(vert_1, w); return r != 0; } - boolean clusterReciprocal_() { - m_hash_values = m_shape.createUserIndex(); - int point_count = m_shape.getTotalPointCount(); - - m_shape.getXY(m_shape.getFirstVertex(m_shape.getFirstPath(m_shape - .getFirstGeometry())), m_origin); - - // This holds clusters. - m_clusters.clear(); - m_clusters.reserveLists(m_shape.getTotalPointCount()); - m_clusters.reserveNodes(m_shape.getTotalPointCount()); - - // Make the hash table. It serves a purpose of fine grain grid. - // Make it 25% larger than the point count to reduce the chance of - // collision. - m_hash_table = new IndexHashTable((point_count * 5) / 4, - new ClusterHashFunction(m_clusters, m_shape, m_origin, - m_tolerance, m_cell_size, m_hash_values)); - m_hash_table.reserveElements(m_shape.getTotalPointCount()); - - boolean b_clustered = false; - - // Go through all vertices stored in the m_shape and put the handles of - // the vertices into the clusters and the hash table. - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape - .getNextGeometry(geometry)) { - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { - assert (vertex != -1); - int cluster = m_clusters.createList(); - m_clusters.addElement(cluster, vertex); // initially each - // cluster consist - // of a single - // vertex - int hash = m_hash_function.calculateHash(cluster); - m_shape.setUserIndex(vertex, m_hash_values, hash); - m_hash_table.addElement(cluster); // add cluster to the hash - // table - vertex = m_shape.getNextVertex(vertex); - } - } - } - - AttributeStreamOfInt32 nn_chain = new AttributeStreamOfInt32(0); - AttributeStreamOfDbl nn_chain_distances = new AttributeStreamOfDbl(0);// array - // of - // distances - // between - // neighbour - // elements - // on - // nn_chain. - // nn_chain_distances->size() - // + - // 1 - // == - // nn_chain->size() - - ClusterCandidate candidate = new ClusterCandidate(); - - // Reciprocal nearest neighbour clustering, using a hash table. - while (m_hash_table.size() != 0 || nn_chain.size() != 0) { - if (nn_chain.size() == 0) { - int cluster = m_hash_table.getAnyElement(); - nn_chain.add(cluster); - continue; - } - - int cluster_1 = nn_chain.getLast(); - int xyindex = m_clusters.getFirstElement(cluster_1); - findClusterCandidate_(xyindex, candidate); - if (candidate.cluster == IndexHashTable.nullNode()) {// no candidate - // for - // clustering - // has been - // found for - // the - // cluster_1. - assert (nn_chain.size() == 1); - nn_chain.removeLast(); - continue; - } - - if (nn_chain.size() == 1) { - m_hash_table.deleteElement(candidate.cluster); - - if (candidate.distance == 0) {// coincident points. Cluster them - // at once. - // cluster xyNearestNeighbour - // with xyindex. The coordinates - // do not need to be changed, - // but weight need to be doubled - m_clusters.concatenateLists(cluster_1, candidate.cluster); - int cluster_weight_1 = m_clusters - .getFirstElement(cluster_1); - int cluster_weight_2 = m_clusters - .getFirstElement(candidate.cluster); - m_shape.setWeight( - cluster_weight_1, - m_shape.getWeight(cluster_weight_1) - + m_shape.getWeight(cluster_weight_2)); - } else - nn_chain.add(candidate.cluster); - - continue; - } - - assert (nn_chain.size() > 1); - if (nn_chain.get(nn_chain.size() - 2) == candidate.cluster) {// reciprocal - // NN - nn_chain.clear(false); - b_clustered |= mergeClusters_(cluster_1, candidate.cluster); - m_hash_table.addElement(cluster_1); - } else { - if (nn_chain_distances.get(nn_chain_distances.size()) <= candidate.distance) {// this - // neighbour - // is - // not - // better - // than - // the - // previous - // one - // (can - // happen - // when - // there - // are - // equidistant - // points). - nn_chain.clear(false); - b_clustered |= mergeClusters_(cluster_1, candidate.cluster); - m_hash_table.addElement(cluster_1); - } else { - nn_chain.add(candidate.cluster); - nn_chain_distances.add(candidate.distance); - } - } - }// while (hashTable->size() != 0 || nn_chain->size() != 0) - - if (b_clustered) { - applyClusterPositions_(); - } - - m_shape.removeUserIndex(m_hash_values); - return b_clustered; - } - boolean clusterNonReciprocal_() { int point_count = m_shape.getTotalPointCount(); - - { - int geometry = m_shape.getFirstGeometry(); - int path = m_shape.getFirstPath(geometry); - int vertex = m_shape.getFirstVertex(path); - m_shape.getXY(vertex, m_origin); + Envelope2D env = m_shape.getEnvelope2D(); + m_origin = env.getLowerLeft(); + double dim = Math.max(env.getHeight(), env.getWidth()); + double mincell = dim / (NumberUtils.intMax() - 1); + if (m_cell_size < mincell) { + m_cell_size = mincell; + m_inv_cell_size = 1.0 / m_cell_size; } // This holds clusters. - if (m_clusters == null) - m_clusters = new IndexMultiList(); - m_clusters.clear(); - m_clusters.reserveLists(m_shape.getTotalPointCount()); - m_clusters.reserveNodes(m_shape.getTotalPointCount()); + m_clusters = new IndexMultiList(); + m_clusters.reserveLists(m_shape.getTotalPointCount() / 3 + 1); + m_clusters.reserveNodes(m_shape.getTotalPointCount() / 3 + 1); m_hash_values = m_shape.createUserIndex(); + m_new_clusters = m_shape.createUserIndex(); // Make the hash table. It serves a purpose of fine grain grid. // Make it 25% larger than the 4 times point count to reduce the chance // of collision. // The 4 times comes from the fact that we check four neighbouring cells // in the grid for each point. - m_hash_function = new ClusterHashFunction(m_clusters, m_shape, - m_origin, m_tolerance, m_cell_size, m_hash_values); - m_hash_table = new IndexHashTable(point_count * 5, m_hash_function); // N - // * - // 4 - // * - // 1.25 - // = - // N - // * - // 5. + m_hash_function = new ClusterHashFunction(m_shape, m_origin, + m_sqr_tolerance, m_inv_cell_size, m_hash_values); + m_hash_table = new IndexHashTable(4 * point_count / 3, m_hash_function); m_hash_table.reserveElements(m_shape.getTotalPointCount()); - boolean b_clustered = false; // Go through all vertices stored in the m_shape and put the handles of @@ -507,44 +475,75 @@ boolean clusterNonReciprocal_() { int vertex = m_shape.getFirstVertex(path); for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { assert (vertex != -1); - int cluster = m_clusters.createList(); - m_clusters.addElement(cluster, vertex); // initially each - // cluster consist - // of a single - // vertex - int hash = m_hash_function.calculateHash(cluster); + int hash = m_hash_function + .calculate_hash_from_vertex(vertex); m_shape.setUserIndex(vertex, m_hash_values, hash); - m_hash_table.addElement(cluster); // add cluster to the hash - // table + m_hash_table.addElement(vertex, hash); // add cluster to the + // hash table + assert (m_shape.getUserIndex(vertex, m_new_clusters) == -1); vertex = m_shape.getNextVertex(vertex); } } } - // m_hash_table.dbg_print_bucket_histogram_(); + // m_hash_table->dbg_print_bucket_histogram_(); {// scope for candidates array AttributeStreamOfInt32 candidates = new AttributeStreamOfInt32(0); - while (m_hash_table.size() != 0) { - int node = m_hash_table.getAnyNode(); - assert (node != IndexHashTable.nullNode()); - int cluster_1 = m_hash_table.getElement(node); - m_hash_table.deleteNode(node); - int xyindex = m_clusters.getFirstElement(cluster_1); - collectClusterCandidates_(xyindex, candidates); - if (candidates.size() == 0) {// no candidate for clustering has - // been found for the cluster_1. - continue; - } + candidates.reserve(10); + + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { + if (m_shape.getUserIndex(vertex, m_new_clusters) == StridedIndexTypeCollection + .impossibleIndex2()) { + vertex = m_shape.getNextVertex(vertex); + continue;// this vertex was merged with another + // cluster. It also was removed from the + // hash table. + } - for (int candidate_index = 0, ncandidates = candidates.size(); candidate_index < ncandidates; candidate_index++) { - int cluster_node = candidates.get(candidate_index); - int cluster = m_hash_table.getElement(cluster_node); - m_hash_table.deleteNode(cluster_node); - b_clustered |= mergeClusters_(cluster_1, cluster); + int hash = m_shape.getUserIndex(vertex, m_hash_values); + m_hash_table.deleteElement(vertex, hash); + + while (true) { + collectClusterCandidates_(vertex, candidates); + if (candidates.size() == 0) {// no candidate for + // clustering has + // been found for + // the cluster_1. + break; + } + + boolean clustered = false; + for (int candidate_index = 0, ncandidates = candidates + .size(); candidate_index < ncandidates; candidate_index++) { + int cluster_node = candidates + .get(candidate_index); + int other_vertex = m_hash_table + .getElement(cluster_node); + m_hash_table.deleteNode(cluster_node); + clustered |= mergeClusters_(vertex, + other_vertex, + candidate_index + 1 == ncandidates); + } + + b_clustered |= clustered; + candidates.clear(false); + // repeat search for the cluster candidates for + // cluster_1 + if (!clustered) + break;// positions did not change + } + + // m_shape->set_user_index(vertex, m_new_clusters, + // Strided_index_type_collection::impossible_index_2()); + vertex = m_shape.getNextVertex(vertex); + } } - m_hash_table.addElement(cluster_1); - candidates.clear(false); } } @@ -552,24 +551,27 @@ boolean clusterNonReciprocal_() { applyClusterPositions_(); } - // m_hash_table.reset(); - // m_hash_function.reset(); + m_hash_table = null; + m_hash_function = null; m_shape.removeUserIndex(m_hash_values); + m_shape.removeUserIndex(m_new_clusters); + // output_debug_printf("total: %d\n",m_shape->get_total_point_count()); + // output_debug_printf("clustered: %d\n",m_dbg_candidate_check_count); return b_clustered; } void applyClusterPositions_() { Point2D cluster_pt = new Point2D(); // move vertices to the clustered positions. - for (int list = m_clusters.getFirstList(); list != IndexMultiList - .nullNode(); list = m_clusters.getNextList(list)) { + for (int list = m_clusters.getFirstList(); list != -1; list = m_clusters + .getNextList(list)) { int node = m_clusters.getFirst(list); - assert (node != IndexMultiList.nullNode()); + assert (node != -1); int vertex = m_clusters.getElement(node); m_shape.getXY(vertex, cluster_pt); - for (node = m_clusters.getNext(node); node != IndexMultiList - .nullNode(); node = m_clusters.getNext(node)) { + for (node = m_clusters.getNext(node); node != -1; node = m_clusters + .getNext(node)) { int vertex_1 = m_clusters.getElement(node); m_shape.setXY(vertex_1, cluster_pt); } @@ -577,8 +579,6 @@ void applyClusterPositions_() { } Clusterer() { - m_hash_values = -1; - m_dbg_candidate_check_count = 0; } } diff --git a/src/main/java/com/esri/core/geometry/CombineOperator.java b/src/main/java/com/esri/core/geometry/CombineOperator.java index e929ca39..43e5ac2c 100644 --- a/src/main/java/com/esri/core/geometry/CombineOperator.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -1,26 +1,26 @@ /* - Copyright 1995-2013 Esri +Copyright 1995-2013 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 +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, - 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. +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 +For additional information, contact: +Environmental Systems Research Institute, Inc. +Attn: Contracts Dept +380 New York Street +Redlands, California, USA 92373 - email: contracts@esri.com - */ +email: contracts@esri.com +*/ package com.esri.core.geometry; import com.esri.core.geometry.Geometry; @@ -35,11 +35,16 @@ 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 and geom2 are the geometry instances 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. + * @return Returns the result geoemtry. In some cases the returned value can point to geom1 or geom2 + * instance. For example, the OperatorIntersection may return geom2 when it is completely + * inside of the geom1. */ public Geometry execute(Geometry geom1, Geometry geom2, - SpatialReference sr, ProgressTracker progressTracker); + SpatialReference sr, ProgressTracker progressTracker); } diff --git a/src/main/java/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java index 6565e114..89db1b0b 100644 --- a/src/main/java/com/esri/core/geometry/ConstructOffset.java +++ b/src/main/java/com/esri/core/geometry/ConstructOffset.java @@ -660,7 +660,7 @@ void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { if (multiPath.isClosedPath(pathIndex)) { // check if last point is a duplicate of first Point2D ptStart = multiPath.getXY(startVertex); - while (multiPath.getXY(endVertex - 1).equals(ptStart)) + while (multiPath.getXY(endVertex - 1).isEqual(ptStart)) endVertex--; // we need at least three points for a polygon @@ -679,11 +679,11 @@ void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { // remove duplicate points at extremities Point2D ptStart = multiPath.getXY(startVertex); while ((startVertex < endVertex) - && multiPath.getXY(startVertex + 1).equals(ptStart)) + && multiPath.getXY(startVertex + 1).isEqual(ptStart)) startVertex++; Point2D ptEnd = multiPath.getXY(endVertex - 1); while ((startVertex < endVertex) - && multiPath.getXY(endVertex - 2).equals(ptEnd)) + && multiPath.getXY(endVertex - 2).isEqual(ptEnd)) endVertex--; // we need at least two points for a polyline diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index cb8b63ee..2a5c6286 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -35,6 +35,30 @@ private CrackAndCluster(ProgressTracker progressTracker) { m_progressTracker = progressTracker; } + static boolean non_empty_points_need_to_cluster(double tolerance, Point pt1, Point pt2) + { + double tolerance_for_clustering = InternalUtils.adjust_tolerance_for_TE_clustering(tolerance); + return Clusterer.isClusterCandidate_(pt1.getX(), pt1.getY(), pt2.getX(), pt2.getY(), MathUtils.sqr(tolerance_for_clustering)); + } + + static Point cluster_non_empty_points(Point pt1, Point pt2, double w1, int rank1, double w2, int rank2) + { + if (rank1 > rank2) + { + return pt1; + } + else if (rank2 < rank1) + { + return pt2; + } + + int [] rank = null; + double [] w = null; + Point pt = new Point(); + Clusterer.mergeVertices(pt1, pt2, w1, rank1, w2, rank2, pt, w, rank); + return pt; + } + public static boolean execute(EditShape shape, double tolerance, ProgressTracker progressTracker) { CrackAndCluster cracker = new CrackAndCluster(progressTracker); @@ -45,41 +69,45 @@ public static boolean execute(EditShape shape, double tolerance, private boolean _cluster(double toleranceCluster) { boolean res = Clusterer.executeNonReciprocal(m_shape, toleranceCluster); - // if (false) - // { - // Geometry geometry = - // m_shape.getGeometry(m_shape.getFirstGeometry());//extract the result - // of simplify - // ((MultiPathImpl)geometry._GetImpl()).SaveToTextFileDbg("c:/temp/_simplifyDbg.txt"); - // } - return res; } - private boolean _crack() { - boolean res = Cracker.execute(m_shape, m_tolerance, m_progressTracker); - // if (false) - // { - // for (int geom = m_shape.getFirstGeometry(); geom != -1; geom = - // m_shape.getNextGeometry(geom)) - // { - // Geometry geometry = m_shape.getGeometry(geom);//extract the result of - // simplify - // ((MultiPathImpl)geometry._getImpl()).SaveToTextFileDbg("c:/temp/_simplifyDbg.txt");//NOTE: - // It ovewrites the previous one! - // } - // } - + private boolean _crack(double tolerance_for_cracking) { + boolean res = Cracker.execute(m_shape, tolerance_for_cracking, m_progressTracker); return res; } private boolean _do() { - double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; + double tol = m_tolerance; + + // Use same tolerances as ArcObjects (2 * sqrt(2) * tolerance for + // clustering) + // sqrt(2) * tolerance for cracking. + // Also, inflate the tolerances slightly to insure the simplified result + // would not change after small rounding issues. + + final double c_factor = 1e-5; + final double c_factor_for_needs_cracking = 1e-6; + double tolerance_for_clustering = InternalUtils + .adjust_tolerance_for_TE_clustering(tol); + double tolerance_for_needs_cracking = InternalUtils + .adjust_tolerance_for_TE_cracking(tol); + double tolerance_for_cracking = tolerance_for_needs_cracking + * (1.0 + c_factor); + tolerance_for_needs_cracking *= (1.0 + c_factor_for_needs_cracking); + + // Require tolerance_for_clustering > tolerance_for_cracking > + // tolerance_for_needs_cracking + assert (tolerance_for_clustering > tolerance_for_cracking); + assert (tolerance_for_cracking > tolerance_for_needs_cracking); + + // double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; boolean bChanged = false; int max_iter = m_shape.getTotalPointCount() + 10 > 30 ? 1000 : (m_shape .getTotalPointCount() + 10) * (m_shape.getTotalPointCount() + 10); int iter = 0; + boolean has_point_features = m_shape.hasPointFeatures(); for (;; iter++) { if (iter > max_iter) throw new GeometryException( @@ -87,29 +115,44 @@ private boolean _do() { // many // iterations - boolean bClustered = _cluster(toleranceCluster); // find close - // vertices and - // clamp them - // together. + boolean bClustered = _cluster(tolerance_for_clustering); // find + // close + // vertices and + // clamp them + // together. bChanged |= bClustered; - boolean bFiltered = (m_shape.filterClosePoints(toleranceCluster, - true) != 0); // remove all degenerate segments. + boolean bFiltered = (m_shape.filterClosePoints( + tolerance_for_clustering, true) != 0); // remove all + // degenerate + // segments. bChanged |= bFiltered; - // _ASSERT(!m_shape.hasDegenerateSegments(toleranceCluster)); - boolean bCracked = _crack(); // crack all segments at intersection - // points and touch points. - bChanged |= bCracked; - if (!bCracked) - break; + boolean b_cracked = false; + if (iter == 0 + || has_point_features + || Cracker.needsCracking(true, m_shape, + tolerance_for_needs_cracking, null, + m_progressTracker)) { + // Cracks only if shape contains segments. + b_cracked = _crack(tolerance_for_cracking); // crack all + // segments at + // intersection + // points and touch + // points. If + // Cracked, then the + // iteration will be + // repeated. + bChanged |= b_cracked; + } + + if (!b_cracked) + break;// was not cracked, so we can bail out. else { // Loop while cracking happens. } - if (m_progressTracker != null - && !m_progressTracker.progress(-1, -1)) - throw new UserCancelException(); + ProgressTracker.checkAndThrow(m_progressTracker); } return bChanged; diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index 822f8bbb..6f320b04 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -40,6 +40,7 @@ class Cracker { private double m_tolerance_cluster; private Treap m_sweep_structure; private SweepComparator m_sweep_comparator; + private boolean m_bAllowCoincident; boolean crackBruteForce_() { EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); @@ -57,9 +58,7 @@ boolean crackBruteForce_() { for (int vertex_1 = iter_1.next(); vertex_1 != -1; vertex_1 = iter_1 .next()) { - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); + ProgressTracker.checkAndThrow(m_progress_tracker); int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); @@ -218,11 +217,11 @@ boolean planeSweep_() { } boolean needsCrackingImpl_() { + boolean b_needs_cracking = false; + if (m_sweep_structure == null) m_sweep_structure = new Treap(); - boolean b_needs_cracking = false; - AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); event_q.reserve(m_shape.getTotalPointCount() + 1); @@ -237,20 +236,21 @@ boolean needsCrackingImpl_() { // create user indices to store edges that end at vertices. int edge_index_1 = m_shape.createUserIndex(); int edge_index_2 = m_shape.createUserIndex(); - m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, true); + m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, !m_bAllowCoincident); m_sweep_structure.setComparator(m_sweep_comparator); - AttributeStreamOfInt32 new_edges = new AttributeStreamOfInt32(0); AttributeStreamOfInt32 swept_edges_to_delete = new AttributeStreamOfInt32( 0); AttributeStreamOfInt32 edges_to_insert = new AttributeStreamOfInt32(0); // Go throught the sorted vertices int event_q_index = 0; + Point2D cluster_pt = new Point2D(); // sweep-line algorithm: for (int vertex = event_q.get(event_q_index++); vertex != -1;) { - Point2D cluster_pt = m_shape.getXY(vertex); + m_shape.getXY(vertex, cluster_pt); + do { int next_vertex = m_shape.getNextVertex(vertex); int prev_vertex = m_shape.getPrevVertex(vertex); @@ -310,16 +310,6 @@ boolean needsCrackingImpl_() { // search. { assert (new_left == -1); - // if (false) - // { - // dbg_print_sweep_structure_(); - // } - // if (false) - // { - // dbg_print_sweep_edge_(edge); - // dbg_print_sweep_edge_(left); - // dbg_print_sweep_edge_(new_left); - // } new_left = left; } @@ -328,19 +318,14 @@ boolean needsCrackingImpl_() { assert (new_right == -1); new_right = right; } - // #ifndef DEBUG - // if (new_left != -1 && new_right != -1) - // break; - // #endif +//#ifdef NDEBUG + if (new_left != -1 && new_right != -1) + break; +//#endif } assert (new_left == -1 || new_left != new_right); - // if (false) - // { - // dbg_print_sweep_structure_(); - // } - m_sweep_comparator.setSweepY(cluster_pt.y, cluster_pt.x); // Delete the edges that terminate at the cluster. @@ -348,9 +333,9 @@ boolean needsCrackingImpl_() { int edge = swept_edges_to_delete.get(i); m_sweep_structure.deleteNode(edge, -1); } - swept_edges_to_delete.resizePreserveCapacity(0); + swept_edges_to_delete.clear(false); - if (new_left != -1 && new_right != -1) { + if (!b_continuing_segment_chain_optimization && new_left != -1 && new_right != -1) { if (checkForIntersections_(new_left, new_right)) { b_needs_cracking = true; m_non_simple_result = m_sweep_comparator.getResult(); @@ -361,9 +346,8 @@ boolean needsCrackingImpl_() { for (int i = 0, n = edges_to_insert.size(); i < n; i += 2) { int v = edges_to_insert.get(i); int otherv = edges_to_insert.get(i + 1); - ; - int new_edge_1; + int new_edge_1 = -1; if (b_continuing_segment_chain_optimization) { new_edge_1 = m_sweep_structure.addElementAtPosition( new_left, new_right, v, true, true, -1); @@ -392,7 +376,6 @@ boolean needsCrackingImpl_() { // vertex. } - // DbgCheckSweepStructure(); if (m_sweep_comparator.intersectionDetected()) { m_non_simple_result = m_sweep_comparator.getResult(); b_needs_cracking = true; @@ -407,16 +390,6 @@ boolean needsCrackingImpl_() { m_shape.setUserIndex(otherv, edge_index_2, new_edge_1); } } - /* - * for (int i = 0, n = new_edges.size(); i < n; i++) { int edge = - * new_edges.get(i); int left = m_sweep_structure.get_prev(edge); - * int right = m_sweep_structure.get_next(edge); if (left != -1) { - * if (check_for_intersections_(left, edge)) { b_needs_cracking = - * true; m_non_simple_result = m_sweep_comparator->get_result(); - * break; } } if (right != -1) { if (check_for_intersections_(right, - * edge)) { b_needs_cracking = true; m_non_simple_result = - * m_sweep_comparator->get_result(); break; } } } - */ if (b_needs_cracking) break; @@ -427,9 +400,6 @@ boolean needsCrackingImpl_() { m_shape.removeUserIndex(edge_index_1); m_shape.removeUserIndex(edge_index_2); - // DEBUGPRINTF("number of compare calls: %d\n", g_dbg); - // DEBUGPRINTF("total point count: %d\n", - // m_shape->get_total_point_count()); return b_needs_cracking; } @@ -450,6 +420,7 @@ boolean checkForIntersections_(int sweep_edge_1, int sweep_edge_2) { // void dbg_check_sweep_structure_(); Cracker(ProgressTracker progress_tracker) { m_progress_tracker = progress_tracker; + m_bAllowCoincident = true; } static boolean canBeCracked(EditShape shape) { @@ -480,13 +451,7 @@ static boolean execute(EditShape shape, Envelope2D extent, { b_cracked = cracker.crackBruteForce_(); } else { - // bool b_cracked_1 = cracker.crack_quad_tree_(); - // bool b_cracked_2 = cracker.crack_quad_tree_(); - // assert(!b_cracked_2); - boolean b_cracked_1 = cracker.crackerPlaneSweep_(); - // bool b_cracked_1 = cracker.crack_quad_tree_(); - // bool b_cracked_1 = cracker.crack_envelope_2D_intersector_(); return b_cracked_1; } return b_cracked; @@ -499,7 +464,7 @@ static boolean execute(EditShape shape, double tolerance, } // Used for IsSimple. - static boolean needsCracking(EditShape shape, double tolerance, + static boolean needsCracking(boolean allowCoincident, EditShape shape, double tolerance, NonSimpleResult result, ProgressTracker progress_tracker) { if (!canBeCracked(shape)) return false; @@ -509,6 +474,7 @@ static boolean needsCracking(EditShape shape, double tolerance, cracker.m_shape = shape; cracker.m_tolerance = tolerance; cracker.m_extent = shape_env; + cracker.m_bAllowCoincident = allowCoincident; if (cracker.needsCrackingImpl_()) { if (result != null) result.Assign(cracker.m_non_simple_result); @@ -525,6 +491,7 @@ static boolean needsCracking(EditShape shape, double tolerance, cracker.m_shape = shape; cracker.m_tolerance = tolerance; cracker.m_extent = shape_env; + cracker.m_bAllowCoincident = allowCoincident; boolean b_res = cracker.needsCrackingImpl_(); transform.setSwapCoordinates(); diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index 66710733..f3f17efe 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -102,7 +102,7 @@ static class CutEvent { static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, Polyline cutter, double tolerance, ArrayList cutPairs, - AttributeStreamOfInt32 segmentCounts) { + AttributeStreamOfInt32 segmentCounts, ProgressTracker progressTracker) { if (cuttee.isEmpty()) { OperatorCutLocal.CutPair cutPair; cutPair = new OperatorCutLocal.CutPair(cuttee, @@ -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, null); + CrackAndCluster.execute(editShape, tolerance, progressTracker); int order = 0; int orderIndex = editShape.createUserIndex(); @@ -1150,7 +1150,7 @@ static boolean _cutterTangents(boolean bConsiderTouch, EditShape shape, return _cutterStartTangents(bConsiderTouch, shape, cutEvents, icutEvent, tangent0, tangent1); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } static boolean _cutterEndTangents(boolean bConsiderTouch, EditShape shape, @@ -1423,6 +1423,6 @@ static boolean _cutteeTangents(EditShape shape, return false; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 565dd98f..23f70df5 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -30,7 +30,7 @@ * geometries in linked lists. It allows constant time manipulation of geometry * vertices. */ -class EditShape { +final class EditShape { interface PathFlags_ { static final int closedPath = 1; static final int exteriorPath = 2; @@ -419,7 +419,7 @@ void splitSegmentForward_(int origin_vertex, SegmentIntersector intersector, int intersector_index) { int last_vertex = getNextVertex(origin_vertex); if (last_vertex == -1) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); Point point = getHelperPoint_(); int path = getPathFromVertex(origin_vertex); int vertex = origin_vertex; @@ -466,7 +466,8 @@ void splitSegmentBackward_(int origin_vertex, SegmentIntersector intersector, int intersector_index) { int last_vertex = getNextVertex(origin_vertex); if (last_vertex == -1) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); + Point point = getHelperPoint_(); int path = getPathFromVertex(origin_vertex); int vertex = origin_vertex; @@ -561,7 +562,7 @@ int addGeometry(Geometry geometry) { if (gt == Geometry.Type.MultiPoint) return addMultiPoint_((MultiPoint) geometry); - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); } // Append a Geometry to the given geometry of the Edit_shape @@ -575,7 +576,7 @@ void appendGeometry(int dstGeometry, Geometry srcGeometry) { return; } - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); } // Adds a path @@ -1075,7 +1076,7 @@ int splitSegment(int origin_vertex, double[] split_scalars, int split_count) { int actual_splits = 0; int next_vertex = getNextVertex(origin_vertex); if (next_vertex == -1) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); int vindex = getVertexIndex(origin_vertex); int vindex_next = getVertexIndex(next_vertex); @@ -1445,7 +1446,7 @@ int insertPath(int geometry, int before_path) { if (before_path != -1) { if (geometry != getGeometryFromPath(before_path)) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); prev = getPrevPath(before_path); } else @@ -1468,6 +1469,39 @@ int insertPath(int geometry, int before_path) { setGeometryPathCount_(geometry, getPathCount(geometry) + 1); return newpath; } + + int insertClosedPath_(int geometry, int before_path, int first_vertex, int checked_vertex, boolean[] contains_checked_vertex) + { + int path = insertPath(geometry, -1); + int path_size = 0; + int vertex = first_vertex; + contains_checked_vertex[0] = false; + while(true) + { + if (vertex == checked_vertex) + contains_checked_vertex[0] = true; + + setPathToVertex_(vertex, path); + path_size++; + int next = getNextVertex(vertex); + assert(getNextVertex(getPrevVertex(vertex)) == vertex); + if (next == first_vertex) + break; + + vertex = next; + } + + setClosedPath(path, true); + setPathSize_(path, path_size); + if (contains_checked_vertex[0]) + first_vertex = checked_vertex; + + setFirstVertex_(path, first_vertex); + setLastVertex_(path, getPrevVertex(first_vertex)); + setRingAreaValid_(path, false); + return path; + } + // Removes a path, gets rid of all its vertices, and returns the next one int removePath(int path) { @@ -1566,7 +1600,7 @@ void closeAllPaths(int geometry) { if (getGeometryType(geometry) == Geometry.GeometryType.Polygon) return; if (!Geometry.isLinear(getGeometryType(geometry))) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { setClosedPath(path, true); @@ -2231,4 +2265,14 @@ void sortVerticesSimpleByX_(AttributeStreamOfInt32 points, int begin_, // The estimated size can be very slightly less than the actual size. // int estimate_memory_size() const; + boolean hasPointFeatures() + { + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) + { + if (!Geometry.isMultiPath(getGeometryType(geometry))) + return true; + } + return false; + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index b684cc23..0675a1e8 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -925,7 +925,7 @@ public void centerAt(Point c) { * @return Returns the lower left corner point. */ public Point getLowerLeft() { - return m_envelope.getLowerLeft(); + return new Point(m_envelope.getLowerLeft()); } /** @@ -934,7 +934,7 @@ public Point getLowerLeft() { * @return Returns the upper right corner point. */ public Point getUpperRight() { - return m_envelope.getUpperRight(); + return new Point(m_envelope.getUpperRight()); } /** @@ -943,7 +943,7 @@ public Point getUpperRight() { * @return Returns the lower right corner point. */ public Point getLowerRight() { - return m_envelope.getLowerRight(); + return new Point(m_envelope.getLowerRight()); } /** @@ -952,7 +952,7 @@ public Point getLowerRight() { * @return Returns the upper left corner point. */ public Point getUpperLeft() { - return m_envelope.getUpperLeft(); + return new Point(m_envelope.getUpperLeft()); } /** @@ -1108,4 +1108,9 @@ public void setYMax(double y) { _touch(); m_envelope.ymax = y; } + + @Override + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 5d982bc1..6f3da52d 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -64,6 +64,7 @@ public void normalize() { public void setEmpty() { vmin = NumberUtils.NaN(); + vmax = NumberUtils.NaN(); } public boolean isEmpty() { @@ -189,4 +190,29 @@ public double getCenter() /* const */ { return 0.5 * (vmin + vmax); } + + @Override + public boolean equals(Object _other) + { + if (_other == this) + return true; + + if (!(_other instanceof Envelope1D)) + return false; + + Envelope1D other = (Envelope1D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (vmin != other.vmin || vmax != other.vmax) + return false; + + return true; + } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(vmin), vmax); + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 89dded08..a6641de9 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -332,10 +332,25 @@ public Point2D queryCorner(int index) { public void queryCorners(Point2D[] corners) { if ((corners == null) || (corners.length < 4)) throw new IllegalArgumentException(); - corners[0] = new Point2D(xmin, ymin); - corners[1] = new Point2D(xmin, ymax); - corners[2] = new Point2D(xmax, ymax); - corners[3] = new Point2D(xmax, ymin); + if (corners[0] != null) + corners[0].setCoords(xmin, ymin); + else + corners[0] = new Point2D(xmin, ymin); + + if (corners[1] != null) + corners[1].setCoords(xmin, ymax); + else + corners[1] = new Point2D(xmin, ymax); + + if (corners[2] != null) + corners[2].setCoords(xmax, ymax); + else + corners[2] = new Point2D(xmax, ymax); + + if (corners[3] != null) + corners[3].setCoords(xmax, ymin); + else + corners[3] = new Point2D(xmax, ymin); } /** @@ -346,10 +361,25 @@ public void queryCorners(Point2D[] corners) { public void queryCornersReversed(Point2D[] corners) { if (corners == null || ((corners != null) && (corners.length < 4))) throw new IllegalArgumentException(); - corners[0] = new Point2D(xmin, ymin); - corners[1] = new Point2D(xmax, ymin); - corners[2] = new Point2D(xmax, ymax); - corners[3] = new Point2D(xmin, ymax); + if (corners[0] != null) + corners[0].setCoords(xmin, ymin); + else + corners[0] = new Point2D(xmin, ymin); + + if (corners[1] != null) + corners[1].setCoords(xmax, ymin); + else + corners[1] = new Point2D(xmax, ymin); + + if (corners[2] != null) + corners[2].setCoords(xmax, ymax); + else + corners[2] = new Point2D(xmax, ymax); + + if (corners[3] != null) + corners[3].setCoords(xmin, ymax); + else + corners[3] = new Point2D(xmin, ymax); } public double getArea() { @@ -516,20 +546,20 @@ public void centerAt(Point c) { ymax = c.getY() + cy; } - public Point getLowerLeft() { - return new Point(xmin, ymin); + public Point2D getLowerLeft() { + return new Point2D(xmin, ymin); } - public Point getUpperLeft() { - return new Point(xmin, ymax); + public Point2D getUpperLeft() { + return new Point2D(xmin, ymax); } - public Point getLowerRight() { - return new Point(xmax, ymin); + public Point2D getLowerRight() { + return new Point2D(xmax, ymin); } - public Point getUpperRight() { - return new Point(xmax, ymax); + public Point2D getUpperRight() { + return new Point2D(xmax, ymax); } public boolean contains(Point p) { @@ -541,7 +571,9 @@ public boolean contains(Point2D p) { } public boolean contains(double x, double y) { - return (!isEmpty() && (x >= xmin && x <= xmax && y >= ymin && y <= ymax)); + // Note: This will return False, if envelope is empty, thus no need to + // call is_empty(). + return x >= xmin && x <= xmax && y >= ymin && y <= ymax; } /** @@ -567,7 +599,7 @@ public boolean containsExclusive(double x, double y) { * Returns True if the envelope contains the point (boundary exclusive). */ public boolean containsExclusive(Point2D pt) { - return contains(pt.x, pt.y); + return containsExclusive(pt.x, pt.y); } /** diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 874e90c5..5fdb9fc3 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -39,17 +39,23 @@ void startConstruction() { reset_(); m_b_add_red_red = true; - if (m_envelopes_red == null) + if (m_envelopes_red == null) { + m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); - else + } else { + m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); + } } - void addEnvelope(Envelope2D envelope) { + void addEnvelope(int element, Envelope2D envelope) { if (!m_b_add_red_red) throw new GeometryException("invalid call"); - m_envelopes_red.add(envelope); + Envelope2D e = new Envelope2D(); + e.setCoords(envelope); + m_elements_red.add(element); + m_envelopes_red.add(e); } void endConstruction() { @@ -68,17 +74,23 @@ void startRedConstruction() { reset_(); m_b_add_red = true; - if (m_envelopes_red == null) + if (m_envelopes_red == null) { + m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); - else + } else { + m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); + } } - void addRedEnvelope(Envelope2D red_envelope) { + void addRedEnvelope(int element, Envelope2D red_envelope) { if (!m_b_add_red) throw new GeometryException("invalid call"); - m_envelopes_red.add(red_envelope); + Envelope2D e = new Envelope2D(); + e.setCoords(red_envelope); + m_elements_red.add(element); + m_envelopes_red.add(e); } void endRedConstruction() { @@ -104,17 +116,23 @@ void startBlueConstruction() { reset_(); m_b_add_blue = true; - if (m_envelopes_blue == null) + if (m_envelopes_blue == null) { + m_elements_blue = new AttributeStreamOfInt32(0); m_envelopes_blue = new ArrayList(0); - else + } else { + m_elements_blue.resizePreserveCapacity(0); m_envelopes_blue.clear(); + } } - void addBlueEnvelope(Envelope2D blue_envelope) { + void addBlueEnvelope(int element, Envelope2D blue_envelope) { if (!m_b_add_blue) throw new GeometryException("invalid call"); - m_envelopes_blue.add(blue_envelope); + Envelope2D e = new Envelope2D(); + e.setCoords(blue_envelope); + m_elements_blue.add(element); + m_envelopes_blue.add(e); } void endBlueConstruction() { @@ -202,7 +220,7 @@ boolean next() { b_searching = resetBlue_(); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -250,9 +268,22 @@ Envelope2D getBlueEnvelope(int handle_b) { return m_envelopes_blue.get(handle_b); } + /* + * Returns the user element associated with handle_a. + */ + int getRedElement(int handle_a) { + return m_elements_red.read(handle_a); + } + + /* + * Returns the user element associated with handle_b. + */ + + int getBlueElement(int handle_b) { + return m_elements_blue.read(handle_b); + } + private double m_tolerance; - private ArrayList m_envelopes_red; - private ArrayList m_envelopes_blue; private int m_sweep_index_red; private int m_sweep_index_blue; private int m_envelope_handle_a; @@ -263,6 +294,11 @@ Envelope2D getBlueEnvelope(int handle_b) { private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_blue; private Envelope2D m_envelope_helper = new Envelope2D(); + private ArrayList m_envelopes_red; + private ArrayList m_envelopes_blue; + private AttributeStreamOfInt32 m_elements_red; + private AttributeStreamOfInt32 m_elements_blue; + private AttributeStreamOfInt32 m_sorted_end_indices_red; private AttributeStreamOfInt32 m_sorted_end_indices_blue; diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 37edf611..18fd6515 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -28,7 +28,7 @@ /** * A class that represents axis parallel 3D rectangle. */ -final class Envelope3D { +public final class Envelope3D { public double xmin; public double ymin; @@ -170,7 +170,7 @@ public void merge(double x, double y, double z) { public void merge(Point3D pt) { merge(pt.x, pt.y, pt.z); } - + public void merge(Envelope3D other) { if (other.isEmpty()) return; diff --git a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java index b39d57fd..b07a73e1 100644 --- a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java +++ b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java @@ -43,4 +43,9 @@ interface GeodeticCurveType { */ public final static int GreatElliptic = 2; public final static int NormalSection = 3; + /*The ShapePreserving type means the segments shapes are preserved in the spatial reference where they are defined. + *The behavior of the ShapePreserving type can be emulated by densifying the geometry with a small step, and then calling a geodetic method + *using Geodesic or GreatElliptic curve types. + */ + public final static int ShapePreserving = 4; } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index fbbee9f3..08e15e40 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -26,8 +26,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.WktExportFlags; + import java.io.ObjectStreamException; import java.io.Serializable; @@ -127,7 +126,7 @@ public int value() { * @return Returns the geometry type. */ public abstract Geometry.Type getType(); - + /** * Returns the topological dimension of the geometry object based on the * geometry's type. @@ -503,18 +502,21 @@ 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(); + static Geometry _clone(Geometry src) { Geometry geom = src.createInstance(); src.copyTo(geom); return geom; } - public String toString() { - String snippet = GeometryEngine.geometryToWkt(this, WktExportFlags.wktExportDefaults); - if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } - return String.format("%s: %s", this.getClass().getSimpleName(), snippet); - } - /** * The stateFlag value changes with changes applied to this geometry. This * allows the user to keep track of the geometry's state. @@ -551,4 +553,19 @@ Object writeReplace() throws ObjectStreamException { geomSerializer.setGeometryByValue(this); return geomSerializer; } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String snippet = OperatorExportToJson.local().execute(null, this); + if (snippet.length() > 200) { + return snippet.substring(0, 197) + "... ("+snippet.length()+" characters)"; + } + else { + return snippet; + } + } + } diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 8208e1a2..756a764f 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -23,6 +23,8 @@ */ package com.esri.core.geometry; +import java.util.ArrayList; + class GeometryAccelerators { // /** @@ -39,6 +41,7 @@ class GeometryAccelerators { private RasterizedGeometry2D m_rasterizedGeometry; private QuadTreeImpl m_quad_tree; + private ArrayList m_path_envelopes; public RasterizedGeometry2D getRasterizedGeometry() { return m_rasterizedGeometry; @@ -48,6 +51,10 @@ public QuadTreeImpl getQuadTree() { return m_quad_tree; } + public ArrayList getPathEnvelopes() { + return m_path_envelopes; + } + void _setRasterizedGeometry(RasterizedGeometry2D rg) { m_rasterizedGeometry = rg; } @@ -55,4 +62,39 @@ void _setRasterizedGeometry(RasterizedGeometry2D rg) { void _setQuadTree(QuadTreeImpl quad_tree) { m_quad_tree = quad_tree; } + + void _setPathEnvelopes(ArrayList pe) { + m_path_envelopes = pe; + } + + static boolean canUseRasterizedGeometry(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } + + return true; + } + + static boolean canUseQuadTree(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } + + if (((MultiVertexGeometry) geom).getPointCount() < 20) { + return false; + } + + return true; + } + + static boolean canUsePathEnvelopes(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } + + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index c8fec93c..66406448 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -24,9 +24,12 @@ package com.esri.core.geometry; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; + +import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.json.JSONException; @@ -53,6 +56,23 @@ public static MapGeometry jsonToGeometry(JsonParser json) { return geom; } + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * @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) throws JsonParseException, IOException { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + /** * Exports the specified geometry instance to it's JSON representation. * @@ -238,38 +258,6 @@ public static Geometry union(Geometry[] geometries, return result.next(); } - // TODO Remove this method from geometry engine - /** - * constructs the set-theoretic difference between an array of geometries - * and another geometry. The dimension of the input geometry has to be equal - * to or greater than that of any element in the array of "geometries". - * - * @param inputGeometries - * an array of geometry objects being subtracted - * @param subtractor - * geometry object to subtract from - * @return any array of geometry objects showing the difference - * */ - static Geometry[] difference(Geometry[] inputGeometries, - Geometry subtractor, SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory - .getOperator(Operator.Type.Difference); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor subtractorCursor = new SimpleGeometryCursor( - subtractor); - GeometryCursor result = op.execute(inputGeometriesCursor, - subtractorCursor, 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 the difference of two geometries. The dimension of geometry2 has * to be equal to or greater than that of geometry1. diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 23e86829..07b11ffd 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -52,4 +52,7 @@ public class GeometryException extends RuntimeException { internalCode = sgCode; } + static GeometryException GeometryInternalError() { + return new GeometryException("internal error"); + } } diff --git a/src/main/java/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java index c9189770..4e60b59e 100644 --- a/src/main/java/com/esri/core/geometry/IndexHashTable.java +++ b/src/main/java/com/esri/core/geometry/IndexHashTable.java @@ -23,7 +23,9 @@ */ package com.esri.core.geometry; -class IndexHashTable { +import java.util.Arrays; + +final class IndexHashTable { // The hash function abstract class that user need to define to use the // IndexHashTable. public static abstract class HashFunction { @@ -38,6 +40,8 @@ public static abstract class HashFunction { int m_random; AttributeStreamOfInt32 m_hashBuckets; + int [] m_bit_filter; //this is aimed to speedup the find + //operation and allows to have less buckets. IndexMultiList m_lists; HashFunction m_hash; @@ -47,6 +51,7 @@ public IndexHashTable(int size, HashFunction hashFunction) { m_hashBuckets = new AttributeStreamOfInt32(size, nullNode()); m_lists = new IndexMultiList(); m_hash = hashFunction; + m_bit_filter = new int[(size*10+31) >> 5]; //10 times more bits than buckets } public void reserveElements(int capacity) { @@ -55,35 +60,76 @@ public void reserveElements(int capacity) { } // Adds new element to the hash table. - public void addElement(int element) { + public int addElement(int element, int hash) { + int bit_bucket = hash % (m_bit_filter.length << 5); + m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) { + list = m_lists.createList(); + m_hashBuckets.set(bucket, list); + } + int node = m_lists.addElement(list, element); + return node; + } + + public int addElement(int element) { int hash = m_hash.getHash(element); + int bit_bucket = hash % (m_bit_filter.length << 5); + m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); int bucket = hash % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) { + if (list == -1) { list = m_lists.createList(); m_hashBuckets.set(bucket, list); } - m_lists.addElement(list, element); + int node = m_lists.addElement(list, element); + return node; } + public void deleteElement(int element, int hash) { + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = -1; + while (ptr != -1) { + int e = m_lists.getElement(ptr); + int nextptr = m_lists.getNext(ptr); + if (e == element) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == -1) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, -1); + } + } else { + prev = ptr; + } + ptr = nextptr; + } + + } + // Removes element from the hash table. public void deleteElement(int element) { int hash = m_hash.getHash(element); int bucket = hash % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) + if (list == -1) throw new IllegalArgumentException(); int ptr = m_lists.getFirst(list); - int prev = IndexMultiList.nullNode(); - while (ptr != IndexMultiList.nullNode()) { + int prev = -1; + while (ptr != -1) { int e = m_lists.getElement(ptr); int nextptr = m_lists.getNext(ptr); if (e == element) { m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == IndexMultiList.nullNode()) { + if (m_lists.getFirst(list) == -1) { m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, IndexMultiList.nullNode()); + m_hashBuckets.set(bucket, -1); } } else { prev = ptr; @@ -96,10 +142,14 @@ public void deleteElement(int element) { // Returns the first node in the hash table bucket defined by the given // hashValue. public int getFirstInBucket(int hashValue) { + int bit_bucket = hashValue % (m_bit_filter.length << 5); + if ((m_bit_filter[(bit_bucket >> 5)] & (1 << (bit_bucket & 0x1F))) == 0) + return -1; + int bucket = hashValue % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) - return IndexMultiList.nullNode(); + if (list == -1) + return -1; return m_lists.getFirst(list); @@ -115,13 +165,8 @@ public int getNextInBucket(int elementHandle) { // the given one. public int findNode(int element) { int hash = m_hash.getHash(element); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) - return IndexMultiList.nullNode(); - - int ptr = m_lists.getFirst(list); - while (ptr != IndexMultiList.nullNode()) { + int ptr = getFirstInBucket(hash); + while (ptr != -1) { int e = m_lists.getElement(ptr); if (m_hash.equal(e, element)) { return ptr; @@ -129,7 +174,7 @@ public int findNode(int element) { ptr = m_lists.getNext(ptr); } - return IndexMultiList.nullNode(); + return -1; } @@ -137,13 +182,8 @@ public int findNode(int element) { // the given element descriptor. public int findNode(Object elementDescriptor) { int hash = m_hash.getHash(elementDescriptor); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) - return IndexMultiList.nullNode(); - - int ptr = m_lists.getFirst(list); - while (ptr != IndexMultiList.nullNode()) { + int ptr = getFirstInBucket(hash);; + while (ptr != -1) { int e = m_lists.getElement(ptr); if (m_hash.equal(elementDescriptor, e)) { return ptr; @@ -151,7 +191,7 @@ public int findNode(Object elementDescriptor) { ptr = m_lists.getNext(ptr); } - return IndexMultiList.nullNode(); + return -1; } @@ -159,7 +199,7 @@ public int findNode(Object elementDescriptor) { public int getNextNode(int elementHandle) { int element = m_lists.getElement(elementHandle); int ptr = m_lists.getNext(elementHandle); - while (ptr != IndexMultiList.nullNode()) { + while (ptr != -1) { int e = m_lists.getElement(ptr); if (m_hash.equal(e, element)) { return ptr; @@ -167,7 +207,7 @@ public int getNextNode(int elementHandle) { ptr = m_lists.getNext(ptr); } - return IndexMultiList.nullNode(); + return -1; } @@ -177,17 +217,17 @@ public void deleteNode(int node) { int hash = m_hash.getHash(element); int bucket = hash % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) + if (list == -1) throw new IllegalArgumentException(); int ptr = m_lists.getFirst(list); - int prev = IndexMultiList.nullNode(); - while (ptr != IndexMultiList.nullNode()) { + int prev = -1; + while (ptr != -1) { if (ptr == node) { m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == IndexMultiList.nullNode()) { + if (m_lists.getFirst(list) == -1) { m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, IndexMultiList.nullNode()); + m_hashBuckets.set(bucket, -1); } return; } @@ -217,11 +257,12 @@ public int getAnyNode() { } public static int nullNode() { - return IndexMultiList.nullNode(); + return -1; } // Removes all elements from the hash table. public void clear() { + Arrays.fill(m_bit_filter, 0); m_hashBuckets = new AttributeStreamOfInt32(m_hashBuckets.size(), nullNode()); m_lists.clear(); diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index 8ba01659..90c84f48 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -130,6 +130,10 @@ static double calculateToleranceFromGeometry(SpatialReference sr, return Math.max(stolerance, gtolerance); } + static double adjust_tolerance_for_TE_clustering(double tol) { return 2.0 * Math.sqrt(2.0) * tol; } + + static double adjust_tolerance_for_TE_cracking(double tol) { return Math.sqrt(2.0) * tol; } + static double calculateToleranceFromGeometry(SpatialReference sr, Geometry geometry, boolean bConservative) { Envelope2D env2D = new Envelope2D(); @@ -249,7 +253,7 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { if (hint_index == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent multipathImpl.calculateEnvelope2D(extent, false); @@ -286,7 +290,7 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, if (hint_index == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent multipathImpl.calculateEnvelope2D(extent, false); @@ -317,7 +321,7 @@ static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { if (element_handle == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent multipointImpl.calculateEnvelope2D(extent, false); @@ -348,7 +352,7 @@ static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, if (element_handle == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent resized_extent = true; @@ -365,8 +369,7 @@ static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, static Envelope2DIntersectorImpl getEnvelope2DIntersector( MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, AttributeStreamOfInt32 verticesA, - AttributeStreamOfInt32 verticesB) { + double tolerance) { Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); multipathImplA.queryLooseEnvelope2D(env_a); multipathImplB.queryLooseEnvelope2D(env_b); @@ -396,8 +399,7 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( b_found_red = true; Envelope2D env = new Envelope2D(); env.setCoords(env_a); - intersector.addRedEnvelope(env); - verticesA.add(segIterA.getStartPointIndex()); + intersector.addRedEnvelope(segIterA.getStartPointIndex(), env); } } intersector.endRedConstruction(); @@ -418,8 +420,7 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( b_found_blue = true; Envelope2D env = new Envelope2D(); env.setCoords(env_b); - intersector.addBlueEnvelope(env); - verticesB.add(segIterB.getStartPointIndex()); + intersector.addBlueEnvelope(segIterB.getStartPointIndex(), env); } } intersector.endBlueConstruction(); @@ -430,10 +431,9 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( return intersector; } - static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( + static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, AttributeStreamOfInt32 parts_a, - AttributeStreamOfInt32 parts_b) { + double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { int type_a = multipathImplA.getType().value(); int type_b = multipathImplB.getType().value(); @@ -447,54 +447,94 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( 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(); + } + 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 (type_a == Geometry.GeometryType.Polygon - && !multipathImplA.isExteriorRing(ipath_a)) + if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon + && !multipathImplA.isExteriorRing(ipath_a)) { continue; + } - multipathImplA.queryPathEnvelope2D(ipath_a, env_a); + if (path_envelopes_a != null) { + Envelope2D env = path_envelopes_a.get(ipath_a); - if (!env_a.isIntersecting(envInter)) - continue; + if (!env.isIntersecting(envInter)) { + continue; + } + + b_found_red = true; + intersector.addRedEnvelope(ipath_a, env); + } else { + multipathImplA.queryPathEnvelope2D(ipath_a, env_a); - b_found_red = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_a); - intersector.addRedEnvelope(env); - parts_a.add(ipath_a); + if (!env_a.isIntersecting(envInter)) { + continue; + } + + b_found_red = true; + Envelope2D env = new Envelope2D(); + env.setCoords(env_a); + intersector.addRedEnvelope(ipath_a, env); + } } intersector.endRedConstruction(); - if (!b_found_red) + if (!b_found_red) { return null; + } + + GeometryAccelerators accel_b = multipathImplB._getAccelerators(); + ArrayList path_envelopes_b = null; + + if (accel_b != null) { + path_envelopes_b = accel_b.getPathEnvelopes(); + } boolean b_found_blue = false; intersector.startBlueConstruction(); for (int ipath_b = 0; ipath_b < multipathImplB.getPathCount(); ipath_b++) { - if (type_b == Geometry.GeometryType.Polygon - && !multipathImplB.isExteriorRing(ipath_b)) + if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon + && !multipathImplB.isExteriorRing(ipath_b)) { continue; + } - multipathImplB.queryPathEnvelope2D(ipath_b, env_b); + if (path_envelopes_b != null) { + Envelope2D env = path_envelopes_b.get(ipath_b); - if (!env_b.isIntersecting(envInter)) - continue; + if (!env.isIntersecting(envInter)) { + continue; + } + + b_found_blue = true; + intersector.addBlueEnvelope(ipath_b, env); + } else { + multipathImplB.queryPathEnvelope2D(ipath_b, env_b); - b_found_blue = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_b); - intersector.addBlueEnvelope(env); - parts_b.add(ipath_b); + if (!env_b.isIntersecting(envInter)) { + continue; + } + + b_found_blue = true; + Envelope2D env = new Envelope2D(); + env.setCoords(env_b); + intersector.addBlueEnvelope(ipath_b, env); + } } intersector.endBlueConstruction(); - if (!b_found_blue) + if (!b_found_blue) { return null; + } return intersector; } diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index 4dd5594c..1237e798 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -112,7 +112,7 @@ int next() { b_searching = initialize_(); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index dd4cf251..135b8463 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -247,7 +247,7 @@ private void next_(int action) { elementEnd_(action); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index f4f2db08..881cdcef 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -26,6 +26,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; /** @@ -80,13 +81,6 @@ boolean _isDegenerate(double tolerance) { return calculateLength2D() <= tolerance; } - @Override - double _calculateSubLength(double t) { - Point2D pt = getCoord2D(t); - pt.sub(getStartXY()); - return pt.length(); - } - // HEADER DEF // Cpp @@ -185,6 +179,16 @@ public Geometry createInstance() { * ((m_yEnd - yorg) + (m_yStart - yorg)) * 0.5; } + @Override + double tToLength(double t) { + return t * calculateLength2D(); + } + + @Override + double lengthToT(double len) { + return len / calculateLength2D(); + } + double getCoordX_(double t) { // double x = m_x_end * t + (1.0 - t) * m_x_start; < 200) { + return snippet.substring(0, 197) + "... ("+snippet.length()+" characters)"; + } + else { + return snippet; + } + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + MapGeometry omg = (MapGeometry)other; + SpatialReference sr = getSpatialReference(); + Geometry g = getGeometry(); + SpatialReference osr = omg.getSpatialReference(); + Geometry og = omg.getGeometry(); + + if (sr != osr) { + if (sr == null || !sr.equals(osr)) + return false; + } + + if (g != og) { + if (g == null || !g.equals(og)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + SpatialReference sr = getSpatialReference(); + Geometry g = getGeometry(); + int hc = 0x2937912; + if (sr != null) + hc ^= sr.hashCode(); + if (g != null) + hc ^= g.hashCode(); + + return hc; + } } diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index 7fca28eb..f6c9311a 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -128,6 +128,13 @@ static double copySign(double x, double y) { return y >= 0.0 ? Math.abs(x) : -Math.abs(x); } + /** + * Calculates sign of the given value. Returns 0 if the value is equal to 0. + */ + static int sign(double value) { + return value < 0 ? -1 : (value > 0) ? 1 : 0; + } + /** * C fmod function. */ diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 34cae2b0..3dbda88d 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -152,6 +152,10 @@ public void queryEnvelope(Envelope env) { public void queryEnvelope2D(Envelope2D env) { m_impl.queryEnvelope2D(env); } + + public void queryPathEnvelope2D(int pathIndex, Envelope2D env) { + m_impl.queryPathEnvelope2D(pathIndex, env); + } @Override void queryEnvelope3D(Envelope3D env) { @@ -179,7 +183,8 @@ public void copyTo(Geometry dst) { m_impl.copyTo((Geometry) dst._getImpl()); } - Geometry getBoundary() { + @Override + public Geometry getBoundary() { return m_impl.getBoundary(); } @@ -187,6 +192,10 @@ Geometry getBoundary() { public void queryCoordinates(Point2D[] dst) { m_impl.queryCoordinates(dst); } + + public void queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, int endIndex) { + m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); + } @Override void queryCoordinates(Point3D[] dst) { @@ -636,31 +645,6 @@ boolean hasNonLinearSegments(int pathIndex) { return m_impl.hasNonLinearSegments(pathIndex); } - // //Elliptic Arc - // //returns true, if parameters of the arc are correct (conform with - // equation of elliptic arc) - // //returns false, if parameters are incorrect. - // //In the latter case the command will be executed anyway, however, the - // EllipseAxes will be automatically corrected - // //to conform with equation of the elliptic arc. - // //Elliptic arcs are stored as rational bezier curves. Use - // CalcEllipticArcParams function - // //to calculate geometric parameters of arc - // //bCW - clockwise (true) or anti-clockwise (false). - // //The type parameter helps to resolve situation, when start and end point - // of arc are too close. - // //It gives a hint to the function how to deal with the situation, when - // start point equal or almost equal to the end point. - // //If type == unknownArc and EndPoint == start point, the arc is a point. - // //If type == minorArc, and abs(SweepAngle - 2 * PI) < 0.001 the arc is - // replaced by the line from start point to EndPoint. - // //If type == majorArc, and abs(SweepAngle) < 0.001 the SweepAngle is - // replaced by SweepAngle + (bCW ? -1. : 1.) * 2. * PI. - // //Here SweepAngle is calculated sweep angle of the arc. - // boolean ArcTo(Point2D endPoint, Point2D ellipseAxes, Point2D center, - // double axisXRotationRad, boolean bCW, enum enumArcType type = - // unknownArc); - /** * Adds a rectangular closed Path to the MultiPathImpl. * @@ -685,22 +669,6 @@ public void addEnvelope(Envelope envSrc, boolean bReverse) { m_impl.addEnvelope(envSrc, bReverse); } - // void AddRoundRect(Envelope2D envSrc, double EllipseWidth, double - // EllipseHeight, boolean bReverse); - - // //Adds ellipse with center point Center and rotation angle between axis X - // and ellipse axes Axes.X equal to RotationAngle. - // boolean AddEllipse(Point2D center, Point2D axes, double rotationAngle, - // boolean bReverse); - - // //adds a pie - a closed figure, consisting of two lines and a segment of - // an ellipse - // //pie is drawn always ccw (when axis X is left-right, axis Y is - // bottom-up). negative sweep angle is converted to positive. - // //angles greater than 2 * pi are converted back into the 2 * pi range. - // boolean AddPie(const DRect & rect, double startAngle, double sweepAngle, - // boolean bReverse); - /** * Returns a SegmentIterator that is set right before the beginning of the * multipath. Calling nextPath() will set the iterator to the first path of diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index a8058804..33e530b6 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -25,6 +25,8 @@ package com.esri.core.geometry; +import java.util.ArrayList; + final class MultiPathImpl extends MultiVertexGeometryImpl { protected boolean m_bPolygon; @@ -291,7 +293,7 @@ public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, public void openPath(int pathIndex) { _touch(); if (m_bPolygon) - throw new GeometryException("internal error");// do not call this + throw GeometryException.GeometryInternalError();// do not call this // method on a // polygon @@ -300,29 +302,27 @@ public void openPath(int pathIndex) { throw new IllegalArgumentException(); if (m_pathFlags == null) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); } - // Reviewed vs. Native Jan 11, 2011 - // Major Changes on 16th of January public void openPathAndDuplicateStartVertex(int pathIndex) { _touch(); if (m_bPolygon) - throw new GeometryException("internal error");// do not call this + throw GeometryException.GeometryInternalError();// do not call this // method on a // polygon int pathCount = getPathCount(); if (pathIndex > pathCount) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); if (!isClosedPath(pathIndex)) return;// do not open if open if (m_pathFlags == null)// if (!m_pathFlags) - throw new GeometryException("nternal_error"); + throw GeometryException.GeometryInternalError(); int oldPointCount = m_pointCount; int pathIndexStart = getPathStart(pathIndex); @@ -355,12 +355,12 @@ public void openPathAndDuplicateStartVertex(int pathIndex) { public void openAllPathsAndDuplicateStartVertex() { _touch(); if (m_bPolygon) - throw new GeometryException("internal error");// do not call this + throw GeometryException.GeometryInternalError();// do not call this // method on a // polygon if (m_pathFlags == null)// if (!m_pathFlags) - throw new GeometryException("nternal_error"); + throw GeometryException.GeometryInternalError(); _verifyAllStreams(); @@ -581,7 +581,7 @@ public void addSegment(Segment segment, boolean bStartNewPath) { segment.queryEnd(point); lineTo(point); } else { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -764,7 +764,7 @@ public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, if (hasNonLinearSegments()) { // TODO: implement me. For example as a while loop over all curves. // Replace, calling ReplaceSegment - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // m_segment_flags->write_range((get_path_start(path_index) + // before_point_index + src_point_count), (oldPointCount - // get_path_start(path_index) - before_point_index), @@ -788,7 +788,7 @@ public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, if (src.hasNonLinearSegments(src_path_index)) { // TODO: implement me. For example as a while loop over all curves. // Replace, calling ReplaceSegment - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } notifyModified(DirtyFlags.DirtyCoordinates); @@ -1058,8 +1058,8 @@ public void insertPoints(int pathIndex, int beforePointIndex, if (srcPathIndex < 0) srcPathIndex = src.getPathCount() - 1; - if (pathIndex > getPathCount() - || beforePointIndex > getPathSize(pathIndex) + if (pathIndex > getPathCount() || beforePointIndex >= 0 + && beforePointIndex > getPathSize(pathIndex) || srcPathIndex >= src.getPathCount() || srcPointCount > src.getPathSize(srcPathIndex)) throw new GeometryException("index out of bounds"); @@ -1148,7 +1148,7 @@ public void insertPoints(int pathIndex, int beforePointIndex, if (src.hasNonLinearSegments(srcPathIndex)) { // TODO: implement me. For example as a while loop over all curves. // Replace, calling ReplaceSegment - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { @@ -1461,7 +1461,8 @@ public double calculatePathLength2D(int pathIndex) /* const */ return sub_length; } - Geometry getBoundary() { + @Override + public Geometry getBoundary() { return Boundary.calculate(this, null); } @@ -1748,7 +1749,7 @@ public void applyTransformation(Transformation2D transform, int pathIndex) { } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -1806,12 +1807,12 @@ public void applyTransformation(Transformation3D transform) { } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } } - + ptStart = transform.transform(ptStart); points.write(ipoint * 2, ptStart.x); points.write(ipoint * 2 + 1, ptStart.y); @@ -2103,7 +2104,8 @@ protected void _updateOGCFlags() { 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 (ipath == 0) + firstSign = area > 0 ? 1 : -1; if (area * firstSign > 0.0) m_pathFlags.setBits(ipath, (byte) PathFlags.enumOGCStartPolygon); @@ -2393,6 +2395,7 @@ public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, rgeom = RasterizedGeometry2D.create(this, toleranceXY, rasterSize); m_accelerators._setRasterizedGeometry(rgeom); + //rgeom.dbgSaveToBitmap("c:/temp/ddd.bmp"); return true; } @@ -2438,11 +2441,11 @@ public void getSegment(int startVertexIndex, SegmentBuffer segBuffer, segBuffer.createLine(); break; case SegmentFlags.enumBezierSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } Segment currentSegment = segBuffer.get(); @@ -2504,7 +2507,7 @@ void queryPathEnvelope2D(int path_index, Envelope2D envelope) { envelope.setCoords(env); } } - + @Override public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { if (m_accelerators == null)// (!m_accelerators) @@ -2520,5 +2523,23 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { return true; } - + + boolean _buildPathEnvelopesAccelerator(GeometryAccelerationDegree d) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } + + ArrayList path_envelopes = new ArrayList(0); + + for (int ipath = 0; ipath < getPathCount(); ipath++) { + Envelope2D env = new Envelope2D(); + queryPathEnvelope2D(ipath, env); + path_envelopes.add(env); + } + + m_accelerators._setPathEnvelopes(path_envelopes); + + 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 008401fd..e890cb8a 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -359,4 +359,9 @@ void setPointByVal(int index, Point pointSrc) { public int getStateFlag() { return m_impl.getStateFlag(); } + + @Override + public Geometry getBoundary() { + return m_impl.getBoundary(); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index 65d7ca48..b29e35c3 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -343,4 +343,9 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree accelDegree) // // TODO Auto-generated method stub // // } + + @Override + public Geometry getBoundary() { + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 60836fa2..fc614cfb 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -561,7 +561,6 @@ public boolean equals(Object other) { if (!(other instanceof MultiVertexGeometryImpl)) return false; - // Borg Implementation MultiVertexGeometryImpl otherMulti = (MultiVertexGeometryImpl) other; if (!(m_description.equals(otherMulti.m_description))) @@ -829,7 +828,7 @@ void _resizeImpl(int pointCount) { } // Checked vs. Jan 11, 2011 - int QueryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, int endIndex) { int endIndexC = endIndex < 0 ? m_pointCount : endIndex; endIndexC = Math.min(endIndexC, beginIndex + dstSize); @@ -841,7 +840,7 @@ int QueryCoordinates(Point2D[] dst, int dstSize, int beginIndex, AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); int j = 0; double[] dstArray = new double[dst.length * 2]; - xy.readRange(2 * beginIndex, endIndexC - beginIndex, dstArray, j, true); + xy.readRange(2 * beginIndex, (endIndexC - beginIndex) * 2, dstArray, j, true); for (int i = 0; i < dst.length; i++) { dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); @@ -932,7 +931,7 @@ void setIsSimple(int isSimpleRes, double tolerance, boolean ogc_known) { _setDirtyFlag(DirtyFlags.IsWeakSimple, true); _setDirtyFlag(DirtyFlags.IsStrongSimple, true); } else - throw new GeometryException("internal error");// what? + throw GeometryException.GeometryInternalError();// what? } double _getSimpleTolerance() { diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 383aa7af..7de7558d 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -38,47 +38,37 @@ public static double snap(double v, double minv, double maxv) { return v < minv ? minv : v > maxv ? maxv : v; } - public static int sizeOf(double v) { + static int sizeOf(double v) { return 8; } - public static int sizeOfDouble() { + static int sizeOfDouble() { return 8; } - public static int sizeOf(int v) { + static int sizeOf(int v) { return 4; } - public static int sizeOf(long v) { + static int sizeOf(long v) { return 8; } - public static int sizeOf(byte v) { + static int sizeOf(byte v) { return 1; } - public static boolean isNaN(double d) { + static boolean isNaN(double d) { return Double.isNaN(d); } - public final static double TheNaN = Double.NaN; + final static double TheNaN = Double.NaN; - public static double NaN() { + static double NaN() { return Double.NaN; } - // public static void main(String[] args) - // { - // System.out.println(Long.toHexString(Double.doubleToLongBits(Double.NaN))); - // System.out.println(Double.longBitsToDouble(0x7FF8000000000001L)); - // - // System.out.println(Long.toHexString(Double.doubleToLongBits(Double.NEGATIVE_INFINITY))); - // System.out.println(Double.longBitsToDouble(0xFFF8000000000000L)); - // System.out.println(Double.NEGATIVE_INFINITY); - // } - - public static int hash(int n) { + static int hash(int n) { int hash = 5381; hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); @@ -88,13 +78,13 @@ public static int hash(int n) { return hash; } - public static int hash(double d) { + static int hash(double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); return hash(hc); } - public static int hash(int hashIn, int n) { + static int hash(int hashIn, int n) { int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); @@ -103,37 +93,37 @@ public static int hash(int hashIn, int n) { return hash; } - public static int hash(int hash, double d) { + static int hash(int hash, double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); return hash(hash, hc); } - public static long doubleToInt64Bits(double d) { + static long doubleToInt64Bits(double d) { return Double.doubleToLongBits(d); } - public static double negativeInf() { + static double negativeInf() { return Double.NEGATIVE_INFINITY; } - public static double positiveInf() { + static double positiveInf() { return Double.POSITIVE_INFINITY; } - public static int intMax() { + static int intMax() { return Integer.MAX_VALUE; } - public static double doubleEps() { + static double doubleEps() { return 2.2204460492503131e-016; } - public static double doubleMax() { + static double doubleMax() { return Double.MAX_VALUE; } - public static int nextRand(int prevRand) { + static int nextRand(int prevRand) { return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, // this is gcc's } diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 691c47f3..20c00787 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -48,7 +48,7 @@ public enum Type { Buffer, Distance, Intersection, Clip, Cut, DensifyByLength, // m_cuts; + ArrayList m_cuts = null; OperatorCutCursor(boolean bConsiderTouch, Geometry cuttee, Polyline cutter, SpatialReference spatialReference, ProgressTracker progressTracker) { - if (cuttee == null) + if (cuttee == null || cutter == null) throw new GeometryException("invalid argument"); m_bConsiderTouch = bConsiderTouch; m_cuttee = cuttee; m_cutter = cutter; - m_tolerance = spatialReference != null ? spatialReference - .getTolerance(Semantics.POSITION) : InternalUtils - .calculateToleranceFromGeometry(null, cuttee, false); - if (m_tolerance > 0.001) - m_tolerance = 0.001; + Envelope2D e = InternalUtils.getMergedExtent(cuttee, cutter); + m_tolerance = InternalUtils.calculateToleranceFromGeometry(spatialReference, e, true); m_cutIndex = -1; - m_cuts = null; + m_progressTracker = progressTracker; } @Override @@ -61,38 +59,47 @@ public int getGeometryID() { @Override public Geometry next() { - if (m_cuts == null) { - int type = m_cuttee.getType().value(); - switch (type) { - case Geometry.GeometryType.Polyline: - m_cuts = _cutPolyline(); - break; - - case Geometry.GeometryType.Polygon: - m_cuts = _cutPolygon(); - break; - } + generateCuts_(); + if (++m_cutIndex < m_cuts.size()) { + return (Geometry)m_cuts.get(m_cutIndex); } - - if (++m_cutIndex < m_cuts.size()) - return (Geometry) m_cuts.get(m_cutIndex); - + return null; } - private ArrayList _cutPolyline() { + private void generateCuts_() { + if (m_cuts != null) + return; + + m_cuts = new ArrayList(); + + Geometry.Type type = m_cuttee.getType(); + switch (type.value()) { + case Geometry.GeometryType.Polyline: + generate_polyline_cuts_(); + break; + + case Geometry.GeometryType.Polygon: + generate_polygon_cuts_(); + break; + + default: + break; // warning fix + } + } + + private void generate_polyline_cuts_() { MultiPath left = new Polyline(); MultiPath right = new Polyline(); MultiPath uncut = new Polyline(); - ArrayList cuts = new ArrayList(2); - cuts.add(left); - cuts.add(right); + m_cuts.add(left); + m_cuts.add(right); ArrayList cutPairs = new ArrayList( 0); Cutter.CutPolyline(m_bConsiderTouch, (Polyline) m_cuttee, m_cutter, - m_tolerance, cutPairs, null); + m_tolerance, cutPairs, null, m_progressTracker); for (int icut = 0; icut < cutPairs.size(); icut++) { OperatorCutLocal.CutPair cutPair = cutPairs.get(icut); @@ -102,78 +109,89 @@ private ArrayList _cutPolyline() { || cutPair.m_side == Side.Coincident) { right.add((MultiPath) cutPair.m_geometry, false); } else if (cutPair.m_side == Side.Undefined) { - cuts.add((MultiPath) cutPair.m_geometry); + m_cuts.add((MultiPath) cutPair.m_geometry); } else { uncut.add((MultiPath) cutPair.m_geometry, false); } } if (!uncut.isEmpty() - && (!left.isEmpty() || !right.isEmpty() || cuts.size() >= 3)) - cuts.add(uncut); + && (!left.isEmpty() || !right.isEmpty() || m_cuts.size() >= 3)) + m_cuts.add(uncut); - return cuts; + if (left.isEmpty() && right.isEmpty() && m_cuts.size() < 3) + m_cuts.clear(); // no cuts } - ArrayList _cutPolygon() { + private void generate_polygon_cuts_() { AttributeStreamOfInt32 cutHandles = new AttributeStreamOfInt32(0); EditShape shape = new EditShape(); int sideIndex = shape.createGeometryUserIndex(); int cutteeHandle = shape.addGeometry(m_cuttee); int cutterHandle = shape.addGeometry(m_cutter); TopologicalOperations topoOp = new TopologicalOperations(); - topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, null); - topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); - Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); - - MultiPath left = new Polygon(); - MultiPath right = new Polygon(); - - ArrayList cuts = new ArrayList(2); - cuts.add(left); - cuts.add(right); - - for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { - Geometry cutGeometry; - { - // intersection - EditShape shapeIntersect = new EditShape(); - int geometryA = shapeIntersect.addGeometry(cutteeRemainder); - int geometryB = shapeIntersect.addGeometry(shape - .getGeometry(cutHandles.get(icutIndex))); - topoOp.setEditShape(shapeIntersect); - int intersectHandle = topoOp.intersection(geometryA, geometryB); - cutGeometry = shapeIntersect.getGeometry(intersectHandle); - - if (cutGeometry.isEmpty()) - continue; - - int side = shape.getGeometryUserIndex( - cutHandles.get(icutIndex), sideIndex); - if (side == 2) - left.add((MultiPath) cutGeometry, false); - else if (side == 1) - right.add((MultiPath) cutGeometry, false); - else - cuts.add((MultiPath) cutGeometry); // Undefined + try { + topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, + m_progressTracker); + topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); + Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); + + MultiPath left = new Polygon(); + MultiPath right = new Polygon(); + + m_cuts.clear(); + m_cuts.add(left); + m_cuts.add(right); + + for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { + Geometry cutGeometry; + { + // intersection + EditShape shapeIntersect = new EditShape(); + int geometryA = shapeIntersect.addGeometry(cutteeRemainder); + int geometryB = shapeIntersect.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeIntersect, m_progressTracker); + int intersectHandle = topoOp.intersection(geometryA, + geometryB); + cutGeometry = shapeIntersect.getGeometry(intersectHandle); + + if (cutGeometry.isEmpty()) + continue; + + int side = shape.getGeometryUserIndex( + cutHandles.get(icutIndex), sideIndex); + if (side == 2) + left.add((MultiPath) cutGeometry, false); + else if (side == 1) + right.add((MultiPath) cutGeometry, false); + else + m_cuts.add((MultiPath) cutGeometry); // Undefined + } + + { + // difference + EditShape shapeDifference = new EditShape(); + int geometryA = shapeDifference + .addGeometry(cutteeRemainder); + int geometryB = shapeDifference.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeDifference, m_progressTracker); + cutteeRemainder = (Polygon) shapeDifference + .getGeometry(topoOp + .difference(geometryA, geometryB)); + } } - { - // difference - EditShape shapeDifference = new EditShape(); - int geometryA = shapeDifference.addGeometry(cutteeRemainder); - int geometryB = shapeDifference.addGeometry(shape - .getGeometry(cutHandles.get(icutIndex))); - topoOp.setEditShape(shapeDifference); - cutteeRemainder = (Polygon) shapeDifference.getGeometry(topoOp - .difference(geometryA, geometryB)); - } - } + if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) + m_cuts.add((MultiPath) cutteeRemainder); - if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) - cuts.add((MultiPath) cutteeRemainder); + if (left.isEmpty() && right.isEmpty()) + m_cuts.clear(); // no cuts - return cuts; + } finally { + topoOp.removeShape(); + } } - } + diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java index d876a60c..b634ff59 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java @@ -72,7 +72,7 @@ else if (geometryType == Geometry.GeometryType.Envelope) return densifyEnvelope((Envelope) geom); else // TODO fix geometry exception to match native implementation - throw new GeometryException("internal error");// GEOMTHROW(internal_error); + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); // unreachable in java // return null; diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 607285a6..11c1295a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -25,7 +25,6 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** * Difference of geometries. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java index f460633a..13d821ed 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java @@ -93,7 +93,7 @@ static int exportToESRIShape(int exportFlags, Geometry geometry, return exportEnvelopeToESRIShape(exportFlags, (Envelope) geometry, shapeBuffer); default: { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // return -1; } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index 3b4fedfe..929e9168 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -97,7 +97,7 @@ private static int exportToWKB(int exportFlags, Geometry geometry, wkbBuffer); default: { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // return -1; } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index a48b1f5c..5fe4398f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -42,7 +42,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export a Polygon as Line/Point : "+export_flags); + throw new IllegalArgumentException("Cannot export a Polygon as (Multi)LineString/(Multi)Point : "+export_flags); exportPolygonToWkt(export_flags, (Polygon) geometry, string); return; @@ -82,13 +82,13 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export an Envelop as (Multi)LineString/(Multi)Point: "+export_flags); + throw new IllegalArgumentException("Cannot export an Envelope as (Multi)LineString/(Multi)Point: "+export_flags); exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); return; default: { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } } diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 14709bda..a77628d3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -83,6 +83,15 @@ public class OperatorFactoryLocal extends OperatorFactory { st_supportedOperators.put(Type.Simplify, new OperatorSimplifyLocal()); st_supportedOperators.put(Type.Offset, new OperatorOffsetLocal()); + st_supportedOperators.put(Type.GeodeticDensifyByLength, + new OperatorGeodeticDensifyLocal()); + + st_supportedOperators.put(Type.ShapePreservingDensify, + new OperatorShapePreservingDensifyLocal()); + + st_supportedOperators.put(Type.GeodesicBuffer, + new OperatorGeodesicBufferLocal()); + st_supportedOperators.put(Type.GeodeticLength, new OperatorGeodeticLengthLocal()); st_supportedOperators.put(Type.GeodeticArea, @@ -118,23 +127,14 @@ public class OperatorFactoryLocal extends OperatorFactory { new OperatorConvexHullLocal()); st_supportedOperators.put(Type.Boundary, new OperatorBoundaryLocal()); - // LabelPoint, - // Simplify, - // + // LabelPoint, - not ported } private OperatorFactoryLocal() { - m_bNewTopo = false;// use sg by default - // m_bNewTopo = true;//use sg by default } - /** - * A temporary way to switch to new topo engine from SG. Set it to true, to - * switch. Need to be changed once at the startup of the program. - */ - boolean m_bNewTopo; /** *Returns a reference to the singleton. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 8e04928a..eb860616 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -65,7 +65,7 @@ private Geometry Generalize(Geometry geom) { return geom; MultiPath mp = (MultiPath) geom; if (mp == null) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); MultiPath dstmp = (MultiPath) geom.createInstance(); Line line = new Line(); for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java new file mode 100644 index 00000000..fd0c9bdf --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -0,0 +1,67 @@ +/* + Copyright 1995-2013 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; + +abstract class OperatorGeodesicBuffer extends Operator { + + @Override + public Operator.Type getType() { + return Operator.Type.GeodesicBuffer; + } + + /** + * Creates a geodesic buffer around the input geometries + * + * @param input_geometries 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. + * @param distancesMeters The buffer distances in meters 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 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 bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. + */ + 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 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. + * @param distanceMeters The buffer distance in meters for the Geometry. + * @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. + */ + abstract public Geometry execute(Geometry inputGeometry, SpatialReference sr, int curveType, double distanceMeters, double maxDeviationMeters, boolean bReserved, ProgressTracker progressTracker); + + public static OperatorGeodesicBuffer local() { + return (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodesicBuffer); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java new file mode 100644 index 00000000..b3475eb9 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 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; + +//This is a stub +class OperatorGeodesicBufferLocal extends OperatorGeodesicBuffer { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, int curveType, double[] distancesMeters, + double maxDeviationMeters, boolean bReserved, boolean bUnion, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry inputGeometry, SpatialReference sr, + int curveType, double distanceMeters, double maxDeviationMeters, + boolean bReserved, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java new file mode 100644 index 00000000..d9a660e6 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -0,0 +1,60 @@ +/* + Copyright 1995-2013 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; + +/** + * Densifies the line segments by length, making them run along specified geodetic curves. + * +* Use this operator to construct geodetic curves. + */ +abstract class OperatorGeodeticDensifyByLength extends Operator { + + @Override + public Type getType() { + return Type.GeodeticDensifyByLength; + } + + /** + * Densifies input geometries. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified segments. + * + * @param geoms The geometries to be densified. + * @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). + * + * Note the behavior is not determined for any geodetic curve segments that connect two poles, or for loxodrome segments that connect to any pole. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, double maxSegmentLengthMeters, SpatialReference sr, int curveType, ProgressTracker progressTracker); + + /** + * Same as above, but works with a single geometry. + */ + public abstract Geometry execute(Geometry geom, double maxSegmentLengthMeters, SpatialReference sr, int curveType, ProgressTracker progressTracker); + + public static OperatorGeodeticDensifyByLength local() { + return (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticDensifyByLength); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java new file mode 100644 index 00000000..5b81f8cf --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -0,0 +1,43 @@ +/* + Copyright 1995-2013 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; + +//This is a stub +public class OperatorGeodeticDensifyLocal extends + OperatorGeodeticDensifyByLength { + + @Override + public GeometryCursor execute(GeometryCursor geoms, + double maxSegmentLengthMeters, SpatialReference sr, int curveType, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry geom, double maxSegmentLengthMeters, + SpatialReference sr, int curveType, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java index d43fe141..47f79482 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java @@ -38,23 +38,6 @@ public Type getType() { return Operator.Type.GeodeticLength; } - /** - * Calculates the geodetic length of each geometry in the geometry cursor. - * - * @param geoms - * The geometry cursor to be iterated over to perform the - * Geodetic Length calculation. - * @param sr - * The SpatialReference of the geometries. - * @param geodeticCurveType - * Use the {@link GeodeticCurveType} interface to choose the - * interpretation of a line connecting two points. - * @param progressTracker - * @return Returns an array of the geoetic lengths of the geometries. - */ - public abstract double[] execute(GeometryCursor geoms, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker); - /** * Calculates the geodetic length of the input Geometry. * diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java index 4c57ca28..e878027d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java @@ -26,12 +26,6 @@ //This is a stub class OperatorGeodeticLengthLocal extends OperatorGeodeticLength { - @Override - public double[] execute(GeometryCursor geoms, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker) { - throw new GeometryException("not implemented"); - } - @Override public double execute(Geometry geom, SpatialReference sr, int geodeticCurveType, ProgressTracker progressTracker) { diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 89cd6632..b5d0c788 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -41,8 +41,9 @@ public MapGeometry execute(int importFlags, Geometry.Type type, MapGeometry mapGeometry = new MapGeometry(geometry, spatialReference); return mapGeometry; } - - static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { + + static JSONArray getJSONArray(JSONObject obj, String name) + throws JSONException { if (obj.get(name) == JSONObject.NULL) return new JSONArray(); else @@ -156,7 +157,8 @@ 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) @@ -237,9 +239,23 @@ private static Geometry polygonTaggedText_(boolean bMultiPolygon, multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - if (!InternalUtils.isClockwiseRing(multiPathImpl, 0)) { - multiPathImpl.reverseAllPaths(); + 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) { diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java index c5ee10aa..6fa9c01a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java @@ -692,8 +692,22 @@ private static Geometry importFromWkbPolygon(boolean bMultiPolygon, polygon.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - if (!InternalUtils.isClockwiseRing(polygon, 0)) - polygon.reverseAllPaths(); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + pathFlags); + + 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(polygon, i)) + polygon.reversePath(i); // make clockwise + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(polygon, i)) + polygon.reversePath(i); // make counter-clockwise + } + } + + polygon.setPathFlagsStreamRef(path_flags_clone); } if ((importFlags & (int) WkbImportFlags.wkbImportNonTrusted) == 0) diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java index d742caa9..3a9bb232 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java @@ -196,8 +196,23 @@ static Geometry polygonTaggedText(boolean b_multi_polygon, multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - if (!InternalUtils.isClockwiseRing(multi_path_impl, 0)) - multi_path_impl.reverseAllPaths(); + 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(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); } if ((import_flags & (int) WktImportFlags.wktImportNonTrusted) == 0) diff --git a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java index cc7fa41d..7f78b59d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java +++ b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java @@ -88,7 +88,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonPoint((Polygon) geomB, (Point) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.Envelope: { @@ -110,7 +110,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonEnvelope( (Polygon) geomB, (Envelope) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.MultiPoint: { @@ -131,7 +131,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonMultiPoint( (Polygon) geomB, (MultiPoint) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.Polyline: { @@ -152,7 +152,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonPolyline( (Polygon) geomB, (Polyline) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.Polygon: { @@ -173,12 +173,12 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return quickTest2DPolygonPolygon((Polygon) geomA, (Polygon) geomB, tolerance); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } default: - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? // return 0; } @@ -581,7 +581,7 @@ private static int quickTest2DPolygonPoint(Polygon geomA, Point2D ptB, if (pipres == PolygonUtils.PiPResult.PiPBoundary) return (int) Relation.Touches;// clementini's touches - throw new GeometryException("internal error");// GEOMTHROW(internal_error); + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); // //what else // return 0; } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index 2416fb06..c1230446 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -25,7 +25,6 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** *Intersection of geometries by a given geometry. @@ -60,7 +59,7 @@ 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 + *@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 *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. * @@ -85,6 +84,7 @@ public abstract GeometryCursor execute(GeometryCursor input_geometries, *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. *@return Returns the intersected Geometry. */ public abstract Geometry execute(Geometry inputGeometry, diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index 9d5437dc..e06670dc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -118,16 +118,16 @@ Geometry intersect(Geometry input_geom) { // m_geomIntersector, m_spatial_reference, m_progress_tracker); // Preprocess geometries to be clipped to the extent of intersection to // get rid of extra segments. - double tol = 0; + double t = InternalUtils.calculateToleranceFromGeometry(m_spatial_reference, commonExtent, true); Envelope2D env = new Envelope2D(); m_geomIntersector.queryEnvelope2D(env); Envelope2D env1 = new Envelope2D(); input_geom.queryEnvelope2D(env1); + env.inflate(2.0 * t, 2.0 * t); env.intersect(env1); assert (!env.isEmpty()); - double t = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, commonExtent, true) * 10; - env.inflate(10 * t, 10 * t); + env.inflate(100 * t, 100 * t); + double tol = 0; Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, 0.0); Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); @@ -191,18 +191,21 @@ GeometryCursor intersectEx(Geometry input_geom) { Envelope2D commonExtent = InternalUtils.getMergedExtent( m_geomIntersector, input_geom); + double t = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, commonExtent, true); + // Preprocess geometries to be clipped to the extent of intersection to // get rid of extra segments. - double tol = 0; + Envelope2D env = new Envelope2D(); m_geomIntersector.queryEnvelope2D(env); + env.inflate(2 * t, 2 * t); Envelope2D env1 = new Envelope2D(); input_geom.queryEnvelope2D(env1); env.intersect(env1); assert (!env.isEmpty()); - double t = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, commonExtent, true) * 10; - env.inflate(10 * t, 10 * t); + env.inflate(100 * t, 100 * t); + double tol = 0; Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, 0.0); Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); @@ -252,6 +255,7 @@ Geometry tryNativeImplementation_(Geometry input_geom) { input_geom.queryEnvelope2D(env2D1); Envelope2D env2D2 = new Envelope2D(); m_geomIntersector.queryEnvelope2D(env2D2); + env2D2.inflate(2.0 * tolerance, 2.0 * tolerance); bResultIsEmpty = !env2D1.isIntersecting(env2D2); } @@ -369,7 +373,7 @@ else if (dim1 == 0) { if (m_geomIntersectorType == Geometry.GeometryType.Point) return TopologicalOperations.intersection( (Point) m_geomIntersector, input_geom, tolerance1); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } // Try Polyline vs Polygon @@ -402,6 +406,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { polygonImpl.queryEnvelope2D(clipEnvelope); Envelope2D env1 = new Envelope2D(); polylineImpl.queryEnvelope2D(env1); + env1.inflate(2.0 * tolerance, 2.0 * tolerance); clipEnvelope.intersect(env1); assert (!clipEnvelope.isEmpty()); } @@ -453,6 +458,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { polygon = (Polygon) clippedPolygon; polygonImpl = (MultiPathImpl) polygon._getImpl(); + accel = polygonImpl._getAccelerators();//update accelerators } if (unresolvedSegments < 0) { diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java index f9adfbca..bb13dcf3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java @@ -65,8 +65,7 @@ public boolean accelerateGeometry(Geometry geometry, if (!canAccelerateGeometry(geometry)) return false; - double tol = spatialReference != null ? spatialReference - .getTolerance(VertexDescription.Semantics.POSITION) : 0; + double tol = InternalUtils.calculateToleranceFromGeometry(spatialReference, geometry, false); boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) ._buildQuadTreeAccelerator(accelDegree); accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) diff --git a/src/main/java/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java index cfd1bec1..da2a1712 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProject.java +++ b/src/main/java/com/esri/core/geometry/OperatorProject.java @@ -71,11 +71,39 @@ public abstract int transform(ProjectionTransformation transform, * @return projected coordinates in the interleaved form. */ public abstract double[] transform(ProjectionTransformation transform, - double[] coordsSrc, int pointCount); + double[] coordsSrc, int pointCount); + + /** + * Folds a geometry into the 360 degree range of the associated spatial reference. If the spatial reference be a 'pannable' PCS or GCS. For other spatial types, the function throws an invalid + * argument exception. A pannable PCS it a Rectangular PCS where the x coordinate range is equivalent to a 360 degree range on the defining geographic Coordinate System(GCS). If the spatial + * reference is a GCS then it is always pannable(default 360 range for spatial reference in GCS coordinates is -180 to 180) + * + * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of + * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. + * Folding does not preserve geodetic area or length. Folding does not preserve perimeter of a polygon. + * + * @param geom The geometry to be folded. + * @param pannableSR The pannable Spatial Reference. + * @return Folded geometry. + */ + public abstract Geometry foldInto360Range(Geometry geom, SpatialReference pannableSR); + + /** + * Same as fold_into_360_range. The difference is that this function preserves geodetic area of polygons and geodetic length of polylines. It does not preserve regular area and length or perimeter + * of polygons. Also, this function might change tangent of the lines at the points of folding. + * + * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of + * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. + * + * @param geom The geometry to be folded. + * @param pannableSR The pannable Spatial Reference. + * @param curveType The type of geodetic curve to use to produce vertices at the points of folding. \return Folded geometry. + */ + public abstract Geometry foldInto360RangeGeodetic(Geometry geom, SpatialReference pannableSR, int curveType); public static OperatorProject local() { return (OperatorProject) OperatorFactoryLocal.getInstance() - .getOperator(Type.Project); + .getOperator(Type.Project); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java index c44c7e1d..78598ca6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java @@ -49,4 +49,15 @@ public double[] transform(ProjectionTransformation transform, throw new GeometryException("not implemented"); } + @Override + public Geometry foldInto360RangeGeodetic(/* const */Geometry _geom, /* const */ + SpatialReference pannableSR, /* GeodeticCurveType */int curveType) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry foldInto360Range(/* const */Geometry geom, /* const */ + SpatialReference pannableSR) { + throw new GeometryException("not implemented"); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java index d8b5b33b..80327e18 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java @@ -36,7 +36,27 @@ public Type getType() { } /** - * Returns the nearest coordinate on the Geometry to the given input point. + *Returns the nearest coordinate on the Geometry to the given input point. + *@param geom The input Geometry. + *@param inputPoint The query point. + *@param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are + *inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, + *but only determine proximity to the boundary. + *@param bCalculateLeftRightSide The function will calculate left/right side of polylines or polygons when the parameter is True. + *\return Returns the result of proximity calculation. See Proximity_2D_result. + */ + public abstract Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide); + + /** + *Returns the nearest coordinate on the Geometry to the given input point. + *@param geom The input Geometry. + *@param inputPoint The query point. + *@param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are + *inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, + *but only determine proximity to the boundary. + *\return Returns the result of proximity calculation. See Proximity_2D_result. */ public abstract Proximity2DResult getNearestCoordinate(Geometry geom, Point inputPoint, boolean bTestPolygonInterior); @@ -74,4 +94,8 @@ public static OperatorProximity2D local() { .getOperator(Type.Proximity2D); } + interface ProxResultInfo { + static final int rightSide = 0x1; + } + } diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java index 89171166..363c29a4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java @@ -30,9 +30,199 @@ class OperatorProximity2DLocal extends OperatorProximity2D { + class Side_helper { + int m_i1; + int m_i2; + boolean m_bRight1; + boolean m_bRight2; + + void reset() { + m_i1 = -1; + m_i2 = -1; + m_bRight1 = false; + m_bRight2 = false; + } + + int find_non_degenerate(SegmentIterator segIter, int vertexIndex, + int pathIndex) { + segIter.resetToVertex(vertexIndex, pathIndex); + + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double length = segment.calculateLength2D(); + + if (length != 0.0) + return segIter.getStartPointIndex(); + } + + segIter.resetToVertex(vertexIndex, pathIndex); + + while (segIter.hasPreviousSegment()) { + Segment segment = segIter.previousSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + int find_prev_non_degenerate(SegmentIterator segIter, int index) { + segIter.resetToVertex(index, -1); + + while (segIter.hasPreviousSegment()) { + Segment segment = segIter.previousSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + int find_next_non_degenerate(SegmentIterator segIter, int index) { + segIter.resetToVertex(index, -1); + segIter.nextSegment(); + + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + void find_analysis_pair_from_index(Point2D inputPoint, + SegmentIterator segIter, int vertexIndex, int pathIndex) { + m_i1 = find_non_degenerate(segIter, vertexIndex, pathIndex); + + if (m_i1 != -1) { + segIter.resetToVertex(m_i1, -1); + Segment segment1 = segIter.nextSegment(); + double t1 = segment1.getClosestCoordinate(inputPoint, false); + Point2D p1 = segment1.getCoord2D(t1); + double d1 = Point2D.sqrDistance(p1, inputPoint); + Point2D pq = new Point2D(); + pq.setCoords(p1); + pq.sub(segment1.getStartXY()); + Point2D pr = new Point2D(); + pr.setCoords(inputPoint); + pr.sub(segment1.getStartXY()); + m_bRight1 = (pq.crossProduct(pr) < 0); + + m_i2 = find_next_non_degenerate(segIter, m_i1); + if (m_i2 != -1) { + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + double t2 = segment2 + .getClosestCoordinate(inputPoint, false); + Point2D p2 = segment2.getCoord2D(t2); + double d2 = Point2D.sqrDistance(p2, inputPoint); + + if (d2 > d1) { + m_i2 = -1; + } else { + pq.setCoords(p2); + pq.sub(segment2.getStartXY()); + pr.setCoords(inputPoint); + pr.sub(segment2.getStartXY()); + m_bRight2 = (pq.crossProduct(pr) < 0); + } + } + + if (m_i2 == -1) { + m_i2 = find_prev_non_degenerate(segIter, m_i1); + if (m_i2 != -1) { + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + double t2 = segment2.getClosestCoordinate(inputPoint, + false); + Point2D p2 = segment2.getCoord2D(t2); + double d2 = Point2D.sqrDistance(p2, inputPoint); + + if (d2 > d1) + m_i2 = -1; + else { + pq.setCoords(p2); + pq.sub(segment2.getStartXY()); + pr.setCoords(inputPoint); + pr.sub(segment2.getStartXY()); + m_bRight2 = (pq.crossProduct(pr) < 0); + + int itemp = m_i1; + m_i1 = m_i2; + m_i2 = itemp; + + boolean btemp = m_bRight1; + m_bRight1 = m_bRight2; + m_bRight2 = btemp; + } + } + } + } + } + + // Try to find two segements that are not degenerate + boolean calc_side(Point2D inputPoint, boolean bRight, + MultiPath multipath, int vertexIndex, int pathIndex) { + SegmentIterator segIter = multipath.querySegmentIterator(); + + find_analysis_pair_from_index(inputPoint, segIter, vertexIndex, + pathIndex); + + if (m_i1 != -1 && m_i2 == -1) {// could not find a pair of segments + return m_bRight1; + } + + if (m_i1 != -1 && m_i2 != -1) { + if (m_bRight1 == m_bRight2) + return m_bRight1;// no conflicting result for the side + else { + // the conflicting result, that we are trying to resolve, + // happens in the obtuse (outer) side of the turn only. + segIter.resetToVertex(m_i1, -1); + Segment segment1 = segIter.nextSegment(); + Point2D tang1 = segment1._getTangent(1.0); + + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + Point2D tang2 = segment2._getTangent(0.0); + + double cross = tang1.crossProduct(tang2); + + if (cross >= 0) // the obtuse angle is on the right side + { + return true; + } else // the obtuse angle is on the right side + { + return false; + } + } + } else { + assert (m_i1 == -1 && m_i2 == -1); + return bRight;// could not resolve the side. So just return the + // old value. + } + } + } + @Override public Proximity2DResult getNearestCoordinate(Geometry geom, Point inputPoint, boolean bTestPolygonInterior) { + + return getNearestCoordinate(geom, inputPoint, bTestPolygonInterior, + false); + } + + @Override + public Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide) { if (geom.isEmpty()) return new Proximity2DResult(); @@ -55,8 +245,8 @@ public Proximity2DResult getNearestCoordinate(Geometry geom, (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); case Geometry.GeometryType.Polyline: case Geometry.GeometryType.Polygon: - return polyPathGetNearestCoordinate((MultiPath) proxmityTestGeom, - inputPoint2D, bTestPolygonInterior); + return multiPathGetNearestCoordinate((MultiPath) proxmityTestGeom, + inputPoint2D, bTestPolygonInterior, bCalculateLeftRightSide); default: { throw new GeometryException("not implemented"); } @@ -129,64 +319,92 @@ public Proximity2DResult[] getNearestVertices(Geometry geom, } } - Proximity2DResult polyPathGetNearestCoordinate(MultiPath geom, - Point2D inputPoint, boolean bTestPolygonInterior) { - Proximity2DResult result = new Proximity2DResult(); + Proximity2DResult multiPathGetNearestCoordinate(MultiPath geom, + Point2D inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide) { + if (geom.getType() == Geometry.Type.Polygon && bTestPolygonInterior) { + Envelope2D env = new Envelope2D(); + geom.queryEnvelope2D(env); + double tolerance = InternalUtils.calculateToleranceFromGeometry( + null, env, false); + + PolygonUtils.PiPResult pipResult; - if (geom.getType() == (Geometry.Type.Polygon) && bTestPolygonInterior) { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - OperatorDisjoint operatorDisjoint = (OperatorDisjoint) factory - .getOperator(Type.Disjoint); + if (bCalculateLeftRightSide) + pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, + inputPoint, 0.0); + else + pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, + inputPoint, tolerance); - Point point = new Point(geom.getDescription()); - point.setXY(inputPoint.x, inputPoint.y); + if (pipResult != PolygonUtils.PiPResult.PiPOutside) { + Proximity2DResult result = new Proximity2DResult(inputPoint, 0, + 0.0); + + if (bCalculateLeftRightSide) + result.setRightSide(true); - boolean disjoint = operatorDisjoint - .execute(geom, point, null, null); - if (!disjoint) { - result._setParams(inputPoint.x, inputPoint.y, 0, 0.0); return result; } } - MultiPathImpl mpImpl = (MultiPathImpl) geom._getImpl(); - SegmentIteratorImpl segIter = mpImpl.querySegmentIterator(); + SegmentIterator segIter = geom.querySegmentIterator(); - Point2D closest = null;// new Point2D(); - int closestIndex = 0; + Point2D closest = new Point2D(); + int closestVertexIndex = -1; + int closestPathIndex = -1; double closestDistanceSq = NumberUtils.doubleMax(); + boolean bRight = false; + int num_candidates = 0; while (segIter.nextPath()) { while (segIter.hasNextSegment()) { Segment segment = segIter.nextSegment(); double t = segment.getClosestCoordinate(inputPoint, false); + Point2D point = segment.getCoord2D(t); double distanceSq = Point2D.sqrDistance(point, inputPoint); if (distanceSq < closestDistanceSq) { + num_candidates = 1; closest = point; - closestIndex = segIter.getStartPointIndex(); + closestVertexIndex = segIter.getStartPointIndex(); + closestPathIndex = segIter.getPathIndex(); closestDistanceSq = distanceSq; + } else if (distanceSq == closestDistanceSq) { + num_candidates++; } } } - result._setParams(closest.x, closest.y, closestIndex, - Math.sqrt(closestDistanceSq)); + Proximity2DResult result = new Proximity2DResult(closest, + closestVertexIndex, Math.sqrt(closestDistanceSq)); - return result; - } + if (bCalculateLeftRightSide) { + segIter.resetToVertex(closestVertexIndex, closestPathIndex); + Segment segment = segIter.nextSegment(); + bRight = (Point2D.orientationRobust(inputPoint, + segment.getStartXY(), segment.getEndXY()) < 0); - Proximity2DResult pointGetNearestVertex(Point geom, Point2D inputPoint) { - Proximity2DResult result = new Proximity2DResult(); + if (num_candidates > 1) { + Side_helper sideHelper = new Side_helper(); + sideHelper.reset(); + bRight = sideHelper.calc_side(inputPoint, bRight, geom, + closestVertexIndex, closestPathIndex); + } - Point2D pt = geom.getXY(); - double distance = Point2D.distance(pt, inputPoint); - result._setParams(pt.x, pt.y, 0, distance); + result.setRightSide(bRight); + } return result; } + Proximity2DResult pointGetNearestVertex(Point geom, Point2D input_point) { + Point2D pt = geom.getXY(); + double distance = Point2D.distance(pt, input_point); + return new Proximity2DResult(pt, 0, distance); + } + Proximity2DResult multiVertexGetNearestVertex(MultiVertexGeometry geom, Point2D inputPoint) { MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom diff --git a/src/main/java/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java index 258a71bf..b1e435bf 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelate.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelate.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; import com.esri.core.geometry.Operator.Type; /** @@ -51,5 +52,19 @@ public static OperatorRelate local() { return (OperatorRelate) OperatorFactoryLocal.getInstance().getOperator( Type.Relate); } + + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RelationalOperations.Accelerate_helper + .can_accelerate_geometry(geometry); + } + + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + return RelationalOperations.Accelerate_helper.accelerate_geometry( + geometry, spatialReference, accelDegree); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java new file mode 100644 index 00000000..3aa5ed8e --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -0,0 +1,71 @@ +/* + Copyright 1995-2013 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; + +/** + * Densifies geometries preserving the shape of the segments in a given spatial reference by length and/or deviation. The elliptic arc lengths of the resulting line segments are no longer than the + * given max length, and the line segments will be closer than the given max deviation to both the original segment curve and the joining elliptic arcs. + */ +abstract class OperatorShapePreservingDensify extends Operator { + + @Override + public Type getType() { + return Type.ShapePreservingDensify; + } + + /** + * Performs the Shape Preserving Densify operation on the geometry set. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the + * densified segments. + * + * @param geoms The geometries to be densified. + * @param sr The spatial reference of the geometries. + * @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). + * + * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); + + /** + * Performs the Shape Preserving Densify operation on the geometry. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified + * segments. + * + * @param geom The geometry to be densified. + * @param sr The spatial reference of the geometry. + * @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). + * + * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); + + public static OperatorShapePreservingDensify local() { + return (OperatorShapePreservingDensify) OperatorFactoryLocal.getInstance() + .getOperator(Type.ShapePreservingDensify); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java new file mode 100644 index 00000000..01bcf3a7 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 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; + +//This is a stub +class OperatorShapePreservingDensifyLocal extends + OperatorShapePreservingDensify { + + @Override + public GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, + double maxLengthMeters, double maxDeviationMeters, double reserved, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry geom, SpatialReference sr, + double maxLengthMeters, double maxDeviationMeters, double reserved, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java index 2cb90a96..f4afeff7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java @@ -41,22 +41,15 @@ public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, @Override public boolean canAccelerateGeometry(Geometry geometry) { - return RasterizedGeometry2D.canUseAccelerator(geometry); + return RelationalOperations.Accelerate_helper + .can_accelerate_geometry(geometry); } - + @Override public boolean accelerateGeometry(Geometry geometry, SpatialReference spatialReference, GeometryAccelerationDegree accelDegree) { - if (!canAccelerateGeometry(geometry)) - return false; - - double tol = spatialReference != null ? spatialReference - .getTolerance(VertexDescription.Semantics.POSITION) : 0; - boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildQuadTreeAccelerator(accelDegree); - accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildRasterizedGeometryAccelerator(tol, accelDegree); - return accelerated; + return RelationalOperations.Accelerate_helper.accelerate_geometry( + geometry, spatialReference, accelDegree); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index 305b2d9d..791f394a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -152,8 +152,8 @@ private boolean testToleranceDistance_(int xyindex1, int xyindex2) { double y1 = m_xy.read(2 * xyindex1 + 1); double x2 = m_xy.read(2 * xyindex2); double y2 = m_xy.read(2 * xyindex2 + 1); - boolean b = !Clusterer.isClusterCandidate(x1, y1, x2, y2, - m_toleranceIsSimple); + boolean b = !Clusterer.isClusterCandidate_(x1, y1, x2, y2, + m_toleranceIsSimple * m_toleranceIsSimple); if (!b) { if (m_geometry.getDimension() == 0) return false; @@ -332,7 +332,7 @@ private boolean checkCrackingPlanesweep_() // cracker,that uses planesweep EditShape editShape = new EditShape(); editShape.addGeometry(m_geometry); NonSimpleResult result = new NonSimpleResult(); - boolean bNonSimple = Cracker.needsCracking(editShape, + boolean bNonSimple = Cracker.needsCracking(false, editShape, m_toleranceIsSimple, result, m_progressTracker); if (bNonSimple) { result.m_vertexIndex1 = editShape @@ -1258,7 +1258,7 @@ private Edge createEdge_(/* const */Segment seg, int xyindex, int pathIndex, if (gt == Geometry.Type.Line) { edge = createEdgeLine_(seg); } else { - throw new GeometryException("internal error"); // implement + throw GeometryException.GeometryInternalError(); // implement // recycling for // curves } @@ -1646,17 +1646,19 @@ MultiVertexGeometry simplifyPlanar_() { m_editShape = new EditShape(); m_editShape.addGeometry(m_geometry); - assert (m_knownSimpleResult != GeometryXSimple.Strong); - if (m_knownSimpleResult != GeometryXSimple.Weak) { - CrackAndCluster.execute(m_editShape, m_toleranceSimplify, - m_progressTracker); - } - - if (m_geometry.getType().equals(Geometry.Type.Polygon)) { - Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), - m_knownSimpleResult); + if (m_editShape.getTotalPointCount() != 0) { + assert (m_knownSimpleResult != GeometryXSimple.Strong); + if (m_knownSimpleResult != GeometryXSimple.Weak) { + CrackAndCluster.execute(m_editShape, m_toleranceSimplify, + m_progressTracker); + } + + if (m_geometry.getType().equals(Geometry.Type.Polygon)) { + Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), + m_knownSimpleResult, false); + } } - + m_geometry = m_editShape.getGeometry(m_editShape.getFirstGeometry()); // extract // the // result @@ -1726,10 +1728,10 @@ else if (gt == Geometry.Type.Envelope) { false)); return bReturnValue ? 1 : 0; } else if (Geometry.isSegment(gt.value())) { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // return seg.IsSimple(m_tolerance); } else if (!Geometry.isMultiVertex(gt.value())) { - throw new GeometryException("internal error");// What else? + throw GeometryException.GeometryInternalError();// What else? } double tolerance = InternalUtils.calculateToleranceFromGeometry( @@ -1824,7 +1826,7 @@ static protected int isSimpleAsFeature(/* const */Geometry geometry, /* const */ } else if (gt == Geometry.Type.Polygon) { knownSimpleResult = helper.polygonIsSimpleAsFeature_(); } else { - throw new GeometryException("internal error");// what else? + throw GeometryException.GeometryInternalError();// what else? } ((MultiVertexGeometryImpl) (geometry._getImpl())).setIsSimple( @@ -1881,7 +1883,7 @@ static int isSimpleOGC(/* const */Geometry geometry, /* const */ || gt == Geometry.Type.Polygon) { knownSimpleResult = helper.isSimplePlanarImpl_(); } else { - throw new GeometryException("internal error");// what else? + throw GeometryException.GeometryInternalError();// what else? } if (result != null) @@ -1954,7 +1956,7 @@ static protected Geometry simplifyAsFeature(/* const */Geometry geometry, /* con } else if (gt == Geometry.Type.Polygon) { result = (Geometry) (helper.polygonSimplifyAsFeature_()); } else { - throw new GeometryException("internal error"); // what else? + throw GeometryException.GeometryInternalError(); // what else? } return result; @@ -1999,12 +2001,11 @@ static Geometry simplifyOGC(/* const */Geometry geometry, /* const */ } if (!Geometry.isMultiVertex(gt.value())) { - throw new GeometryException("internal error"); // what else? + throw new GeometryException("OGC simplify is not implemented for this geometry type" + gt); } - MultiVertexGeometry result = TopologicalOperations.planarSimplify( - (MultiVertexGeometry) geometry, tolerance, false, false, - progressTracker); + MultiVertexGeometry result = TopologicalOperations.simplifyOGC( + (MultiVertexGeometry) geometry, tolerance, false, progressTracker); return result; } @@ -2134,9 +2135,6 @@ private static final class EdgeComparerForSelfIntersection implements @Override public int compare(Edge e1, Edge e2) { - // C++ style with bool operator() would be - // return parent->edgeAngleCompare_(*e1,*e2) < 0; - return parent.edgeAngleCompare_(e1, e2); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java index 8f6bce0f..aa9c5a9f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java @@ -25,7 +25,6 @@ class OperatorSimplifyLocalOGC extends OperatorSimplifyOGC { - // Reviewed vs. Feb 8 2011 @Override public GeometryCursor execute(GeometryCursor geoms, SpatialReference spatialRef, boolean bForceSimplify, @@ -43,7 +42,6 @@ public boolean isSimpleOGC(Geometry geom, SpatialReference spatialRef, return res > 0; } - // Reviewed vs. Feb 8 2011 @Override public Geometry execute(Geometry geom, SpatialReference spatialRef, boolean bForceSimplify, ProgressTracker progressTracker) { diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index 17a2f5bf..bb914e77 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -24,7 +24,6 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** * Symmetric difference (XOR) operation between geometries. diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index 95cd8f93..69340bd2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -25,14 +25,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** * * Union of geometries. * */ -public abstract class OperatorUnion extends Operator implements CombineOperator { +public abstract class OperatorUnion extends Operator implements CombineOperator{ @Override public Type getType() { return Type.Union; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java index cfc46871..53164ef2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java @@ -25,37 +25,119 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; -class OperatorUnionCursor extends GeometryCursor { +final class OperatorUnionCursor extends GeometryCursor { private GeometryCursor m_inputGeoms; private ProgressTracker m_progress_tracker; private SpatialReferenceImpl m_spatial_reference; - private int m_index; - private boolean m_b_done; + private int m_index = -1; + private boolean m_b_done = false; + private boolean [] m_had_geometry = new boolean[4]; + private int [] m_dim_geom_counts = new int [4]; + private boolean m_b_union_all_dimensions = false; + private int m_max_dimension = -1; + private int m_added_geoms = 0; + private int m_current_dim = -1; + + private final static class Geom_pair + { + void init() { geom = null; vertex_count = -1; unioned = false; } + Geometry geom; + int vertex_count; + boolean unioned;//true if geometry is a result of union operation + } + + final static class Bin_type //bin array and the total vertex count in the bin + { + int bin_vertex_count = 0; + ArrayList geometries = new ArrayList(); + + void add_pair(Geom_pair geom) + { + bin_vertex_count += geom.vertex_count; + geometries.add(geom); + } + void pop_pair() + { + bin_vertex_count -= geometries.get(geometries.size() - 1).vertex_count; + geometries.remove(geometries.size() - 1); + } + Geom_pair back_pair() { return geometries.get(geometries.size() - 1); } + int geom_count() { return geometries.size(); } + } + + ArrayList< TreeMap > m_union_bins = new ArrayList< TreeMap >();//for each dimension there is a list of bins sorted by level OperatorUnionCursor(GeometryCursor inputGeoms1, SpatialReference sr, ProgressTracker progress_tracker) { - m_index = -1; - m_b_done = false; m_inputGeoms = inputGeoms1; m_spatial_reference = (SpatialReferenceImpl) (sr); m_progress_tracker = progress_tracker; - // Otherwise, unsupported use patternes could be produced. + } + + private Geometry get_result_geometry(int dim) { + assert (m_dim_geom_counts[dim] > 0); + java.util.TreeMap map = m_union_bins.get(dim); + Map.Entry e = map.firstEntry(); + Bin_type bin = e.getValue(); + + Geometry resG; + resG = bin.back_pair().geom; + boolean unioned = bin.back_pair().unioned; + map.remove(e.getKey()); + + if (unioned) { + resG = OperatorSimplify.local().execute(resG, m_spatial_reference, + false, m_progress_tracker); + if (dim == 0 && resG.getType() == Geometry.Type.Point) {// must + // return + // multipoint + // for + // points + MultiPoint mp = new MultiPoint(resG.getDescription()); + if (!resG.isEmpty()) + mp.add((Point) resG); + + resG = mp; + } + } - startDissolve(); + return resG; } @Override public Geometry next() { - if (m_b_done) + if (m_b_done && m_current_dim == m_max_dimension) return null; - m_b_done = true;// m_b_done is added to avoid calling - // m_inputGeoms->next() second time after it returned - // NULL. - - return dissolve_(); + while (!step_()) { + } + + if (m_max_dimension == -1) + return null;// empty input cursor + + if (m_b_union_all_dimensions) { + m_current_dim++; + while (true) { + if (m_current_dim > m_max_dimension || m_current_dim < 0) + throw GeometryException.GeometryInternalError(); + + if (m_had_geometry[m_current_dim]) + break; + } + + m_index++; + return get_result_geometry(m_current_dim); + } else { + m_index = 0; + assert (m_max_dimension >= 0); + m_current_dim = m_max_dimension; + return get_result_geometry(m_max_dimension); + } } @Override @@ -63,289 +145,143 @@ public int getGeometryID() { return m_index; } - private void step(){ - if (!bFinished) { - Geometry geom = m_inputGeoms.next(); - - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - - if (geom != null) { - if (geom.getDimension() > dimension) - { - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = geom; - int sz = getVertexCount_(geom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - { - unionBins.clear(); - int resize = Math.max(16, level + 1); - for (int i = 0; i < resize; i++) - unionBins.add(null); - binSizes.resize(resize, 0); - unionBins.set(level, new ArrayList(0)); - unionBins.get(level).add(pair); - binSizes.set(level, sz); - totalToUnion = 1; - totalVertexCount = sz; - dimension = geom.getDimension(); - } - } else if (!geom.isEmpty() - && geom.getDimension() == dimension) { - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = geom; - int sz = getVertexCount_(geom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - { - int resize = Math.max(unionBins.size(), level + 1); - if (resize > unionBins.size()) { - int grow = resize - unionBins.size(); - for (int i = 0; i < grow; i++) - unionBins.add(null); - } - binSizes.resize(resize, 0); - if (unionBins.get(level) == null) - unionBins - .set(level, new ArrayList(0)); - - unionBins.get(level).add(pair); - binSizes.write(level, binSizes.read(level) + sz); - - totalToUnion++; - totalVertexCount += sz; - } - } else { - // skip empty or geometries of lower dimension - } - } else { - bFinished = true; - } - } + private boolean step_(){ + if (m_b_done) + return true; - while (true)// union features that are in the unionBins + Geometry geom = null; + if (m_inputGeoms != null) { - if (!bFinished) {// when we are still loading geometries, union - // geometries of the same level, starting - // with the biggest level. - int imax = -1; - int maxSz = 0; - // Find a bin that contains more than one geometry and has - // the max vertex count. - for (int i = 0, n = unionBins.size(); i < n; i++) { - if (unionBins.get(i) != null - && unionBins.get(i).size() > 1 - && binSizes.read(i) > binVertexThreshold) { - if (maxSz < binSizes.read(i)) { - maxSz = binSizes.read(i); - imax = i; - } - } - } - - if (maxSz > 0) { - // load the found bin into the batchToUnion. - while (unionBins.get(imax).size() > 0) { - ArrayList bin = unionBins.get(imax); - batchToUnion.add(bin.get(bin.size() - 1)); - bin.remove(bin.size() - 1); - totalVertexCount -= batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - imax, - binSizes.read(imax) - - batchToUnion.get(batchToUnion - .size() - 1).vertex_count); - } - } - } else if (totalToUnion > 1) {// bFinished_shared == true - we - // loaded all geometries - int level = 0; - int vertexCount = 0; - for (int i = 0, n = unionBins.size(); i < n - && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold); i++) { - if (unionBins.get(i) != null) { - while (!unionBins.get(i).isEmpty() - && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold)) { - ArrayList bin = unionBins.get(i); - batchToUnion.add(bin.get(bin.size() - 1)); - bin.remove(bin.size() - 1); - level = i; - totalVertexCount -= batchToUnion - .get(batchToUnion.size() - 1).vertex_count; - vertexCount += batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - i, - binSizes.read(i) - - batchToUnion.get(batchToUnion - .size() - 1).vertex_count); - continue; - } - } - } - - if (batchToUnion.size() == 1)// never happens? - {// only one element. Put it back. - unionBins.get(level).add( - batchToUnion.get(batchToUnion.size() - 1)); - totalVertexCount += batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - level, - binSizes.read(level) - + batchToUnion.get(batchToUnion.size() - 1).vertex_count); - batchToUnion.remove(batchToUnion.size() - 1); - } + geom = m_inputGeoms.next(); + if (geom == null) { + m_b_done = true; + m_inputGeoms = null; } - - if (!batchToUnion.isEmpty()) { - Geometry resGeom; - int resDim; - ArrayList geoms = new ArrayList(0); - geoms.ensureCapacity(batchToUnion.size()); - for (int i = 0, n = batchToUnion.size(); i < n; i++) { - geoms.add(batchToUnion.get(i).geom); - } - - resGeom = TopologicalOperations.dissolveDirty(geoms, - m_spatial_reference, m_progress_tracker); - // assert(Operator_factory_local::get_instance()->CanDoNewTopo(pair1.geom->get_geometry_type(), - // pair2.geom->get_geometry_type())); - // resGeom = - // Topological_operations::dissolve(batchToUnion[0].geom, - // batchToUnion[1].geom, m_spatial_reference, - // m_progress_tracker); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_dissolve.txt", - // *resGeom, nullptr); - resDim = resGeom.getDimension(); - - dissolved_something = true; - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = resGeom; - int sz = getVertexCount_(resGeom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - int resize = Math.max(unionBins.size() + 1, level); - if (resize > unionBins.size()) { - int grow = resize - unionBins.size(); - for (int i = 0; i < grow; i++) - unionBins.add(null); - } - binSizes.resize(resize, 0); - - if (unionBins.get(level) == null) - unionBins.set(level, new ArrayList(0)); - - unionBins.get(level).add(pair); - binSizes.write(level, binSizes.read(level) + sz); - totalToUnion -= (batchToUnion.size() - 1); - - batchToUnion.clear(); - } else { - boolean bCanGo = totalToUnion == 1; - if (bFinished) - bLocalDone = true; - - break; + } + + ProgressTracker.checkAndThrow(m_progress_tracker); + + if (geom != null) { + int dim = geom.getDimension(); + m_had_geometry[dim] = true; + if (dim >= m_max_dimension && !m_b_union_all_dimensions) + { + add_geom(dim, false, geom); + if (dim > m_max_dimension && !m_b_union_all_dimensions) + { + //this geometry has higher dimension than the previously processed one + //Therefore we delete all lower dimensions (unless m_b_union_all_dimensions is true). + remove_all_bins_with_lower_dimension(dim); + } } + else + { + //this geometry is skipped + } + } else { + //geom is null. do nothing } + + 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; + } + } + } + } + } + + return m_b_done; } - boolean bLocalDone = false; - int dimension = -1; - boolean bFinished = false; - int totalToUnion = 0; - int totalVertexCount = 0; - int binVertexThreshold = 10000; - boolean dissolved_something = false; - - ArrayList batchToUnion = new ArrayList(0); - ArrayList> unionBins = new ArrayList>( - 0); - AttributeStreamOfInt32 binSizes = new AttributeStreamOfInt32(0); - - private void startDissolve() { - m_index = m_inputGeoms.getGeometryID(); - - // Geometries are placed into the unionBins. - // Each bin stores geometries of certain size range. - // The bin number is calculated as log(N), where N is the number of - // vertices in geoemtry and the log is to a - // certain base (now it is 4). - unionBins.ensureCapacity(128); - binSizes.reserve(128); - - for (int i = 0; i < 16; i++) - unionBins.add(null); - - batchToUnion.ensureCapacity(32); + ArrayList collect_geometries_to_union(int dim) { + ArrayList batch_to_union = new ArrayList(); + ArrayList> entriesToRemove = new ArrayList>(); + Set> set = m_union_bins.get(dim) + .entrySet(); + for (Map.Entry e : set) { + //int level = e.getKey(); + Bin_type bin = e.getValue(); + + final int binVertexThreshold = 10000; + + if (m_b_done + || (bin.bin_vertex_count > binVertexThreshold && bin + .geom_count() > 1)) { + m_dim_geom_counts[dim] -= bin.geom_count(); + m_added_geoms -= bin.geom_count(); + while (bin.geometries.size() > 0) { + // empty geometries will be unioned too. + batch_to_union.add(bin.back_pair().geom); + bin.pop_pair(); + } + + entriesToRemove.add(e); + } + } + + set.removeAll(entriesToRemove); + return batch_to_union; } - @Override - public boolean tock() { - if (!m_b_done) - { - step(); + private void remove_all_bins_with_lower_dimension(int dim) { + // this geometry has higher dimension than the previously processed one + for (int i = 0; i < dim; i++) { + m_union_bins.get(i).clear(); + m_added_geoms -= m_dim_geom_counts[i]; + m_dim_geom_counts[i] = 0; } - return bFinished; } - private Geometry dissolve_() { - while (!bLocalDone) { - step(); - } - - Geometry resGeom = null; - for (int i = 0; i < unionBins.size(); i++) { - if (unionBins.get(i) != null && unionBins.get(i).size() > 0) - resGeom = unionBins.get(i).get(0).geom; + private void add_geom(int dimension, boolean unioned, Geometry geom) { + Geom_pair pair = new Geom_pair(); + pair.init(); + pair.geom = geom; + int sz = get_vertex_count_(geom); + pair.vertex_count = sz; + int level = get_level_(sz); + if (dimension + 1 > (int) m_union_bins.size()) { + for (int i = 0, n = Math.max(2, dimension + 1); i < n; i++) { + m_union_bins.add(new TreeMap()); + } } - if (resGeom == null) - return resGeom; - - if (dissolved_something) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorSimplify simplify = (OperatorSimplify) engine - .getOperator(Operator.Type.Simplify); - resGeom = simplify.execute(resGeom, m_spatial_reference, false, - m_progress_tracker); + Bin_type bin = m_union_bins.get(dimension).get(level);//return null if level is abscent + if (bin == null) { + bin = new Bin_type(); + m_union_bins.get(dimension).put(level, bin); } - if (resGeom.getType().value() == Geometry.GeometryType.Point) {// must - // return - // multipoint - // for - // points - MultiPoint mp = new MultiPoint(resGeom.getDescription()); - if (!resGeom.isEmpty()) - mp.add((Point) resGeom); - resGeom = mp; - } + pair.unioned = unioned; + bin.add_pair(pair); - return resGeom; + // Update global cursor state + m_dim_geom_counts[dimension]++; + m_added_geoms++; + m_max_dimension = Math.max(m_max_dimension, dimension); } - private static final class GeomPair { - void init() { - geom = null; - vertex_count = -1; - } - - Geometry geom; - int vertex_count; + private static int get_level_(int sz) {// calculates logarithm of sz to base + // 4. + return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) + : (int) 0; } - private static int getVertexCount_(Geometry geom) { + private static int get_vertex_count_(Geometry geom) { int gt = geom.getType().value(); if (Geometry.isMultiVertex(gt)) { return ((MultiVertexGeometry) geom).getPointCount(); @@ -356,13 +292,12 @@ private static int getVertexCount_(Geometry geom) { } else if (Geometry.isSegment(gt)) { return 2; } else { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } - - private static int getLevel_(int sz) {// calculates logarithm of sz to base - // 4. - return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) - : (int) 0; + + @Override + public boolean tock() { + return step_(); } } diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 175f485a..ab23f7c1 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -60,21 +60,14 @@ boolean sweep(EditShape shape, double tolerance) { m_b_cracked = false; m_tolerance = tolerance; m_tolerance_sqr = tolerance * tolerance; - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"PlaneSweep along x\n"); - // } - // #endif boolean b_cracked = sweepImpl_(); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"PlaneSweep along y\n"); - // } - // #endif shape.applyTransformation(transform); - fillEventQueuePass2(); - b_cracked |= sweepImpl_(); + if (!b_cracked) { + fillEventQueuePass2(); + b_cracked |= sweepImpl_(); + } + m_shape.removeUserIndex(m_vertex_cluster_index); m_shape = null; return m_b_cracked; @@ -372,7 +365,7 @@ void addEdgeToCluster(int edge, int cluster) { assert (getEdgeCluster(edge, 0) != cluster); setEdgeCluster_(edge, 1, cluster); } else - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); addEdgeToClusterImpl_(edge, cluster);// simply adds the edge to the list // of cluster edges. @@ -527,16 +520,9 @@ void mergeEdges_(int edge1, int edge2) { // Merged edges have different clusters (clusters have not yet been // merged) // merge clusters before merging the edges - Point2D pt11 = getClusterXY(cluster_1); - Point2D pt21 = getClusterXY(cluster21); - // #ifdef _DEBUG_TOPO - // Point_2D pt12, pt22; - // pt12 = get_cluster_xy(cluster2); - // pt22 = get_cluster_xy(cluster22); - // assert((pt11.is_equal(pt21) && pt12.is_equal(pt22)) || - // (pt12.is_equal(pt21) && pt11.is_equal(pt22))); - // #endif - if (pt11.isEqual(pt21)) { + getClusterXY(cluster_1, pt_1); + getClusterXY(cluster21, pt_2); + if (pt_1.isEqual(pt_2)) { if (cluster_1 != cluster21) { mergeClusters_(cluster_1, cluster21); assert (!m_modified_clusters.hasElement(cluster21)); @@ -776,13 +762,12 @@ void processSplitHelper1_(int index, int edge, // sweep structure. int count = intersector.getResultSegmentCount(index); Segment seg = intersector.getResultSegment(index, 0); - Point2D newStart = seg.getStartXY(); + seg.getStartXY(pt_2); int clusterStart = getEdgeCluster(edge, 0); - Point2D pt = getClusterXY(clusterStart); - if (!pt.isEqual(newStart)) { - int res1 = pt.compare(m_sweep_point); - int res2 = newStart.compare(m_sweep_point); - //if (pt.compare(m_sweep_point) > 0 && newStart.compare(m_sweep_point) < 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, @@ -797,13 +782,12 @@ void processSplitHelper1_(int index, int edge, } seg = intersector.getResultSegment(index, count - 1); - Point2D newEnd = seg.getEndXY(); + seg.getEndXY(pt_2); int clusterEnd = getEdgeCluster(edge, 1); - pt = getClusterXY(clusterEnd); - if (!pt.isEqual(newEnd)) { - int res1 = pt.compare(m_sweep_point); - int res2 = newEnd.compare(m_sweep_point); - //if (pt.compare(m_sweep_point) > 0 && newEnd.compare(m_sweep_point) < 0) + 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. @@ -850,8 +834,6 @@ boolean checkAndFixIntersection_(int leftSweepNode, int rightSweepNode) { } void fixIntersection_(int left, int right) { - // static int dbg = 0; - // dbg++; m_b_cracked = true; int edge1 = m_sweep_structure.getElement(left); int edge2 = m_sweep_structure.getElement(right); @@ -897,24 +879,6 @@ void fixIntersection_(int left, int right) { m_complications = true; - // #ifdef _DEBUG_CRACKING_REPORT - // { - // for (int resi = 0; resi < 2; resi++) - // { - // DEBUGPRINTF(L"intersection result %d:\n", resi); - // for (int i = 0; i < - // m_segment_intersector.get_result_segment_count(resi); i++) - // { - // Point_2D pt_1 = m_segment_intersector.get_result_segment(resi, - // i)->get_start_xy(); - // Point_2D pt_2 = m_segment_intersector.get_result_segment(resi, - // i)->get_end_xy(); - // DEBUGPRINTF(L"(%0.17f, %0.17f --- %0.17f, %0.17f)\n", pt_1.x, - // pt_1.y, pt_2.x, pt_2.y); - // } - // } - // } - // #endif splitEdge_(edge1, edge2, -1, m_segment_intersector); m_segment_intersector.clear(); } @@ -933,38 +897,13 @@ void fixIntersectionPointSegment_(int cluster, int node) { seg_1 = m_line_1; } - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt11, pt12, pt21; - // pt11 = seg_1->get_start_xy(); - // pt12 = seg_1->get_end_xy(); - // get_cluster_xy(cluster, pt21); - // DEBUGPRINTF(L"Intersecting edge %d (%0.4f, %0.4f - %0.4f, %0.4f) and cluster %d (%0.4f, %0.4f)\n", - // edge1, pt11.x, pt11.y, pt12.x, pt12.y, cluster, pt21.x, pt21.y); - // } - // #endif - int clusterVertex = getClusterFirstVertex(cluster); m_segment_intersector.pushSegment(seg_1); m_shape.queryPoint(clusterVertex, m_helper_point); m_segment_intersector.intersect(m_tolerance, m_helper_point, 0, 1.0, true); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"intersection result\n"); - // for (int i = 0; i < - // m_segment_intersector.get_result_segment_count(0); i++) - // { - // Point_2D pt_1 = m_segment_intersector.get_result_segment(0, - // i)->get_start_xy(); - // Point_2D pt_2 = m_segment_intersector.get_result_segment(0, - // i)->get_end_xy(); - // DEBUGPRINTF(L"(%0.17f, %0.17f --- %0.17f, %0.17f)\n", pt_1.x, - // pt_1.y, pt_2.x, pt_2.y); - // } - // } - // #endif + splitEdge_(edge1, -1, cluster, m_segment_intersector); m_segment_intersector.clear(); @@ -974,8 +913,6 @@ void insertNewEdges_() { if (m_edges_to_insert_in_sweep_structure.size() == 0) return; - // dbg_check_new_edges_array_(); - while (m_edges_to_insert_in_sweep_structure.size() != 0) { if (m_edges_to_insert_in_sweep_structure.size() > Math.max( (int) 100, m_shape.getTotalPointCount())) { @@ -987,10 +924,8 @@ void insertNewEdges_() { // iterate on the data one more time. } - int edge = m_edges_to_insert_in_sweep_structure - .get(m_edges_to_insert_in_sweep_structure.size() - 1); - m_edges_to_insert_in_sweep_structure - .resize(m_edges_to_insert_in_sweep_structure.size() - 1); + int edge = m_edges_to_insert_in_sweep_structure.getLast(); + m_edges_to_insert_in_sweep_structure.removeLast(); assert (getEdgeSweepNode(edge) == StridedIndexTypeCollection .impossibleIndex3()); @@ -1005,37 +940,18 @@ void insertNewEdges_() { boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { assert (getEdgeSweepNode(edge) == -1); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt11, pt12; - // int c_1 = get_edge_cluster(edge, 0); - // int c_2 = get_edge_cluster(edge, 1); - // get_cluster_xy(c_1, pt11); - // get_cluster_xy(c_2, pt12); - // DEBUGPRINTF(L"Inserting edge %d (%0.4f, %0.4f - %0.4f, %0.4f) to sweep structure\n", - // edge, pt11.x, pt11.y, pt12.x, pt12.y); - // } - // #endif int newEdgeNode; if (m_b_continuing_segment_chain_optimization) { - // st_counter_insertions++; - // st_counter_insertions_optimized++; newEdgeNode = m_sweep_structure.addElementAtPosition( m_prev_neighbour, m_next_neighbour, edge, true, true, -1); m_b_continuing_segment_chain_optimization = false; } else { - // st_counter_insertions++; - // st_counter_insertions_unique++; newEdgeNode = m_sweep_structure.addUniqueElement(edge, -1); } if (newEdgeNode == -1) {// a coinciding edge. int existingNode = m_sweep_structure.getDuplicateElement(-1); int existingEdge = m_sweep_structure.getElement(existingNode); - // #ifdef _DEBUG_CRACKING_REPORT - // DEBUGPRINTF(L"Edge %d is a duplicate of %d. Merged\n", edge, - // existingEdge); - // #endif mergeEdges_(existingEdge, edge); return false; } @@ -1044,10 +960,6 @@ boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { setEdgeSweepNode_(edge, newEdgeNode); if (m_sweep_comparator.intersectionDetected()) { - // #ifdef _DEBUG_CRACKING_REPORT - // DEBUGPRINTF(L"intersection detected\n"); - // #endif - // The edge has been inserted into the sweep structure and an // intersection has beebn found. The edge will be split and removed. m_sweep_comparator.clearIntersectionDetectedFlag(); @@ -1062,11 +974,13 @@ boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { return false; } + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); int isEdgeOnSweepLine_(int edge) { int cluster_1 = getEdgeCluster(edge, 0); int cluster2 = getEdgeCluster(edge, 1); - Point2D pt_1 = getClusterXY(cluster_1); - Point2D pt_2 = getClusterXY(cluster2); + getClusterXY(cluster_1, pt_1); + getClusterXY(cluster2, pt_2); if (Point2D.sqrDistance(pt_1, pt_2) <= m_tolerance_sqr) {// avoid // degenerate // segments @@ -1095,18 +1009,14 @@ void fillEventQueue() { // clusters EditShape.VertexIterator iter = m_shape.queryVertexIterator(); for (int vert = iter.next(); vert != -1; vert = iter.next()) { - event_q.add(vert); + if (m_shape.getUserIndex(vert, m_vertex_cluster_index) != -1) + event_q.add(vert); } - assert (m_shape.getTotalPointCount() == event_q.size()); - // Now we can merge coincident clusters and form the envent structure. // sort vertices lexicographically. m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); - // int perPoint = m_shape->estimate_memory_size() / - // m_shape->get_total_point_count(); - // perPoint = 0; // The m_event_q is the event structure for the planesweep algorithm. // We could use any data structure that allows log(n) insertion and @@ -1127,9 +1037,10 @@ void fillEventQueue() { Point2D cluster_pt = new Point2D(); cluster_pt.setNaN(); int cluster = -1; + Point2D pt = new Point2D(); for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { int vertex = event_q.get(index); - Point2D pt = m_shape.getXY(vertex); + m_shape.getXY(vertex, pt); if (pt.isEqual(cluster_pt)) { int vertexCluster = m_shape.getUserIndex(vertex, m_vertex_cluster_index); @@ -1139,7 +1050,7 @@ void fillEventQueue() { cluster = getClusterFromVertex(vertex); // add a vertex to the event queue - cluster_pt = m_shape.getXY(vertex); + m_shape.getXY(vertex, cluster_pt); int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this // method // does @@ -1222,18 +1133,6 @@ void updateClusterXY(int cluster, Point2D pt) { // m_edges_to_insert_in_sweep_structure. void splitEdge_(int edge1, int edge2, int intersectionCluster, SegmentIntersector intersector) { - // #ifdef _DEBUG_CRACKING_REPORT - // { - // if (edge2 != -1) - // DEBUGPRINTF(L"Splitting edge1 (%d) and edge2 (%d)\n", edge1, edge2); - // else - // DEBUGPRINTF(L"Splitting edge (%d)\n", edge1); - // } - // #endif - // dbg_check_edge_(edge1); - // - // if (edge2 != -1) - // dbg_check_edge_(edge2); disconnectEdge_(edge1);// disconnects the edge from the clusters. The // edge still remembers the clusters. @@ -1250,9 +1149,9 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, processSplitHelper1_(1, edge2, intersector); if (intersectionCluster != -1) { - Point2D pt = intersector.getResultPoint().getXY(); - Point2D ptCluster = getClusterXY(intersectionCluster); - if (!ptCluster.isEqual(pt)) + intersector.getResultPoint().getXY(pt_1); + getClusterXY(intersectionCluster, pt_2); + if (!pt_2.isEqual(pt_1)) m_modified_clusters.add(intersectionCluster); } @@ -1302,17 +1201,6 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, int vertex = getClusterFirstVertex(cluster); assert (getClusterFromVertex(vertex) == cluster); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt; - // m_shape->get_xy(vertex, pt); - // DEBUGPRINTF(L"Inserting vertex %d, cluster %d (%0.3f, %0.3f)\n", - // vertex, cluster, pt.x, pt.y); - // } - // #endif - // st_counter_insertions++; - // st_counter_insertions_unique++; - eventQnode = m_event_q.addUniqueElement(vertex, -1);// O(logN) // operation if (eventQnode == -1) {// the cluster is coinciding with another @@ -1321,17 +1209,10 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, int v = m_event_q.getElement(existingNode); assert (m_shape.isEqualXY(vertex, v)); int existingCluster = getClusterFromVertex(v); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt; - // m_shape->get_xy(v, pt); - // DEBUGPRINTF(L"Already in the queue %d, cluster %d (%0.3f, %0.3f)\n", - // v, existingCluster, pt.x, pt.y); - // } - // #endif mergeClusters_(existingCluster, cluster); - } else + } else { setClusterEventQNode_(cluster, eventQnode); + } } else { // if already inserted (probably impossible) case } @@ -1341,11 +1222,9 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, } // Returns a cluster's xy. - Point2D getClusterXY(int cluster) { - Point2D p = new Point2D(); + void getClusterXY(int cluster, Point2D ptOut) { int vindex = getClusterVertexIndex(cluster); - m_shape.getXYWithIndex(vindex, p); - return p; + m_shape.getXYWithIndex(vindex, ptOut); } int getClusterFirstVertex(int cluster) { @@ -1383,12 +1262,6 @@ boolean sweepImpl_() { int vertex = m_event_q.getElement(eventQnode); m_sweep_point_cluster = getClusterFromVertex(vertex); m_shape.getXY(vertex, m_sweep_point); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"next event node. Cluster %d, Vertex %d, (%0.3f, %0.3f)\n", - // m_sweep_point_cluster, vertex, m_sweep_point.x, m_sweep_point.y); - // } - // #endif m_sweep_comparator.setSweepY(m_sweep_point.y, m_sweep_point.x);// move // the @@ -1408,7 +1281,7 @@ boolean sweepImpl_() { setEdgeSweepNode_(edge, c_3);// mark that its in // m_edges_to_insert_in_sweep_structure } else if (sweepNode != c_3) { - // assert(StridedIndexTypeCollection.isValidElement(sweepNode)); + assert(StridedIndexTypeCollection.isValidElement(sweepNode)); edgesToDelete.add(sweepNode); } edge = getNextEdge(edge, m_sweep_point_cluster); @@ -1427,8 +1300,6 @@ boolean sweepImpl_() { m_b_continuing_segment_chain_optimization = (edgesToDelete .size() == 1 && m_edges_to_insert_in_sweep_structure .size() == 1); - // st_counter_insertions_all_potential += - // m_edges_to_insert_in_sweep_structure.size() > 0; // Mark nodes that need to be deleted by setting c_2 to the // edge's sweep node member. @@ -1486,11 +1357,9 @@ boolean sweepImpl_() { // Now check if the left and right we found intersect or not. if (left != -1 && right != -1) { - boolean bIntersected = checkAndFixIntersection_(left, right); - if (bIntersected) { - // We'll insert the results of intersection below in - // insert_new_edges_ - m_b_continuing_segment_chain_optimization = false; + if (!m_b_continuing_segment_chain_optimization) { + boolean bIntersected = checkAndFixIntersection_(left, + right); } } else { if ((left == -1) && (right == -1)) @@ -1572,12 +1441,6 @@ void setEditShape_(EditShape shape) { int path_size = m_shape.getPathSize(path); assert (path_size > 1); int first_vertex = m_shape.getFirstVertex(path); - // #ifdef _DEBUG_TOPO - // LOCALREFCLASS(Line, line); - // m_shape.query_line_connector(first_vertex, line); - // assert(line.calculate_length_2D() > m_tolerance);//no - // degenerate lines at the input - // #endif // first------------------ int firstCluster = newCluster_(first_vertex); @@ -1586,24 +1449,31 @@ void setEditShape_(EditShape shape) { int prevEdge = first_edge; int vertex = m_shape.getNextVertex(first_vertex); for (int index = 0, n = path_size - 2; index < n; index++) { + int nextvertex = m_shape.getNextVertex(vertex); // ------------x------------ int cluster = newCluster_(vertex); addEdgeToCluster(prevEdge, cluster); int newEdge = newEdge_(vertex); addEdgeToCluster(newEdge, cluster); prevEdge = newEdge; - vertex = m_shape.getNextVertex(vertex); + vertex = nextvertex; } // ------------------lastx - int cluster = newCluster_(vertex); - addEdgeToCluster(prevEdge, cluster); - if (m_shape.isClosedPath(path)) {// close the path - // lastx------------------firstx + if (m_shape.isClosedPath(path)) { + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + // close the path + // lastx------------------firstx int newEdge = newEdge_(vertex); addEdgeToCluster(newEdge, cluster); addEdgeToCluster(newEdge, firstCluster); + } else { + // ------------------lastx + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); } + } } diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 30db22e7..11459be5 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -25,6 +25,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; /** @@ -66,6 +67,10 @@ public Point(double x, double y) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setXY(x, y); } + public Point(Point2D pt) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setXY(pt); + } /** * Creates a 3D point with specified X, Y and Z coordinates. In case of @@ -91,7 +96,7 @@ public Point(double x, double y, double z) { /** * Returns XY coordinates of this point. */ - Point2D getXY() { + public final Point2D getXY() { if (isEmptyImpl()) throw new GeometryException( "This operation should not be performed on an empty geometry."); @@ -101,11 +106,22 @@ Point2D getXY() { return pt; } + /** + * 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]); + } + /** * Sets the XY coordinates of this point. param pt The point to create the X * and Y coordinate from. */ - void setXY(Point2D pt) { + public final void setXY(Point2D pt) { _touch(); setXY(pt.x, pt.y); } @@ -602,4 +618,9 @@ public int hashCode() { } return hashCode; } + + @Override + public Geometry getBoundary() { + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 319cd5dd..cb673ff5 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -63,11 +63,32 @@ public void setCoords(Point2D other) { public boolean isEqual(Point2D other) { return x == other.x && y == other.y; } + + public boolean isEqual(double x_, double y_) { + return x == x_ && y == y_; + } public boolean isEqual(Point2D other, double tol) { return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); } + public boolean equals(Point2D other) { + return x == other.x && y == other.y; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof Point2D)) + return false; + + Point2D v = (Point2D)other; + + return x == v.x && y == v.y; + } + public void sub(Point2D other) { x -= other.x; y -= other.y; @@ -451,5 +472,10 @@ public static int orientationRobust(Point2D p, Point2D q, Point2D r) { return det_mp.signum(); } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(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 34a901fc..f5d343ff 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -30,7 +30,7 @@ * Basic 3D point class. * */ -final class Point3D { +public final class Point3D { public double x; public double y; public double z; @@ -68,7 +68,7 @@ public void normalize() { z /= len; } - double length() { + public double length() { return Math.sqrt(x * x + y * y + z * z); } @@ -78,11 +78,11 @@ public Point3D(double x, double y, double z) { this.z = z; } - Point3D sub(Point3D other) { + public Point3D sub(Point3D other) { return new Point3D(x - other.x, y - other.y, z - other.z); } - Point3D mul(double factor) { + public Point3D mul(double factor) { return new Point3D(x * factor, y * factor, z * factor); } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 2e173acd..8faea700 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -123,13 +123,13 @@ public void setXY(int i, double x, double y) { } - void interpolateAttributes(int path_index, int from_point_index, + public void interpolateAttributes(int path_index, int from_point_index, int to_point_index) { m_impl.interpolateAttributes(path_index, from_point_index, to_point_index); } - void interpolateAttributes(int semantics, int path_index, + public void interpolateAttributes(int semantics, int path_index, int from_point_index, int to_point_index) { m_impl.interpolateAttributesForSemantics(semantics, path_index, from_point_index, to_point_index); diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index e8f700ab..005446d0 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -246,8 +246,9 @@ else if (polygon.getType() == Geometry.Type.Envelope) { // 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(MultiPathImpl polygonImpl, int iRing1, + 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()) @@ -264,7 +265,7 @@ static boolean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, } if (res == 2 /* (int)PiPResult.PiPBoundary */) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); if (res == 1 /* (int)PiPResult.PiPInside */) return true; @@ -397,28 +398,4 @@ else if (Geometry.isSegment(gt.value())) { throw new GeometryException("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. public static - * booleanean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, int - * iRing2, double tolerance) { SegmentIteratorImpl segIter = - * polygonImpl.querySegmentIterator(); segIter.resetToPath(iRing1); if - * (!segIter.nextPath() || !segIter.hasNextSegment()) throw new - * GeometryException("corrupted geometry"); - * - * PiPResult res = PiPResult.PiPBoundary; - * - * while ((res == PiPResult.PiPBoundary) && segIter.hasNextSegment()) { - * Segment segment = segIter.nextSegment(); Point2D point = - * segment.getCoord2D(0.5); res = - * PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, point, - * tolerance); } - * - * if (res == PiPResult. PiPBoundary) throw new - * GeometryException("internal error"); if (res == PiPResult.PiPInside) - * return true; - * - * return false; } - */ } diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 194bbfb6..2124a0f4 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -95,13 +95,13 @@ public void addSegment(Segment segment, boolean bStartNewPath) { m_impl.addSegment(segment, bStartNewPath); } - void interpolateAttributes(int from_path_index, int from_point_index, - int to_path_index, int to_point_index) { + public void interpolateAttributes(int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { m_impl.interpolateAttributes(from_path_index, from_point_index, to_path_index, to_point_index); } - void interpolateAttributes(int semantics, int from_path_index, + public void interpolateAttributes(int semantics, int from_path_index, int from_point_index, int to_path_index, int to_point_index) { m_impl.interpolateAttributesForSemantics(semantics, from_path_index, from_point_index, to_path_index, to_point_index); diff --git a/src/main/java/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java index d185b991..d4153375 100644 --- a/src/main/java/com/esri/core/geometry/ProgressTracker.java +++ b/src/main/java/com/esri/core/geometry/ProgressTracker.java @@ -34,4 +34,13 @@ public abstract class ProgressTracker { *@return true, if the operation can continue. Returns False, when the operation has to terminate due to a user cancelation. */ public abstract boolean progress(int step, int totalExpectedSteps); + + /** + * Checks the tracker and throws UserCancelException if tracker is not null and progress returns false + * @param tracker can be null, then the method does nothing. + */ + public static void checkAndThrow(ProgressTracker tracker) { + if (tracker != null && !tracker.progress(-1, -1)) + throw new UserCancelException(); + } } diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java index 01a1a2ae..04ac528d 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResult.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResult.java @@ -32,6 +32,21 @@ public class Proximity2DResult { Point2D m_coordinate = new Point2D(); int m_vertexIndex; double m_distance; + int m_info; + + /** + * Sets the right_side info to true or false. + * + * @param bRight + * Whether the nearest coordinate is to the right or left of the + * geometry. + */ + public void setRightSide(boolean bRight) { + if (bRight) + m_info |= (int) OperatorProximity2D.ProxResultInfo.rightSide; + else + m_info &= ~(int) OperatorProximity2D.ProxResultInfo.rightSide; + } /** * Returns TRUE if the Proximity2DResult is empty. This only happens if the @@ -81,6 +96,13 @@ public double getDistance() { return m_distance; } + /** + *Returns true if the closest coordinate is to the right of the MultiPath. + */ + public boolean isRightSide() { + return (m_info & (int) OperatorProximity2D.ProxResultInfo.rightSide) != 0; + } + void _setParams(double x, double y, int vertexIndex, double distance) { m_coordinate.x = x; m_coordinate.y = y; @@ -101,4 +123,11 @@ void _setParams(double x, double y, int vertexIndex, double distance) { Proximity2DResult() { m_vertexIndex = -1; } + + Proximity2DResult(Point2D coordinate, int vertexIndex, double distance) { + m_coordinate.setCoords(coordinate); + m_vertexIndex = vertexIndex; + m_distance = distance; + m_info = 0; + } } diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 8a927447..84c16f67 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -159,15 +159,8 @@ int next() { child_extents[2] = new Envelope2D(); child_extents[3] = new Envelope2D(); } - 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 + setChildExtents_(current_extent, child_extents); m_quads_stack.removeLast(); m_extents_stack.remove(m_extents_stack.size() - 1); @@ -351,15 +344,6 @@ void removeElement(int element_handle) { } } - /** - * Removes the quad and all its children corresponding to the input - * quad_handle. \param quad_handle The handle corresponding to the quad to - * be removed. - */ - void removeQuad(int quad_handle) { - removeQuad_(quad_handle); - } - /** * Returns the element at the given element_handle. \param element_handle * The handle corresponding to the element to be retrieved. @@ -481,14 +465,9 @@ private void reset_(Envelope2D extent, int height) { private int insert_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { - Point2D bb_center = new Point2D(); - bounding_box.queryCenter(bb_center); - - Envelope2D current_extent = new Envelope2D(); - current_extent.setCoords(quad_extent); + if (!quad_extent.contains(bounding_box)) { + assert (!b_flushing); - int current_quad_handle = quad_handle; - if (!current_extent.contains(bounding_box)) { if (height == 0) return -1; @@ -496,8 +475,16 @@ private int insert_(int element, Envelope2D bounding_box, int height, b_flushing, flushed_element_handle); } - // Should this memory be cached for reuse? - Point2D quad_center = new Point2D(); + if (!b_flushing) { + for (int q = quad_handle; q != -1; q = getParent_(q)) + setSubTreeElementCount_(q, getSubTreeElementCount_(q) + 1); + } + + Envelope2D current_extent = new Envelope2D(); + current_extent.setCoords(quad_extent); + + int current_quad_handle = quad_handle; + Envelope2D[] child_extents = new Envelope2D[4]; child_extents[0] = new Envelope2D(); child_extents[1] = new Envelope2D(); @@ -507,46 +494,40 @@ private int insert_(int element, Envelope2D bounding_box, int height, int current_height; for (current_height = height; current_height < m_height && canPushDown_(current_quad_handle); current_height++) { - 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 - - // Find the first child quadrant that contains the bounding box, and - // recursively insert into that child (greedy algorithm) - double mind = NumberUtils.doubleMax(); - int quadrant = -1; + setChildExtents_(current_extent, child_extents); + + boolean b_contains = false; + for (int i = 0; i < 4; i++) { - child_extents[i].queryCenter(quad_center); - double d = Point2D.sqrDistance(quad_center, bb_center); - if (d < mind) { - mind = d; - quadrant = i; - } - } + if (child_extents[i].contains(bounding_box)) { + b_contains = true; + + int child_handle = getChild_(current_quad_handle, i); + if (child_handle == -1) + child_handle = createChild_(current_quad_handle, i); - if (child_extents[quadrant].contains(bounding_box)) { - int child_handle = getChild_(current_quad_handle, quadrant); - if (child_handle == -1) - child_handle = createChild_(current_quad_handle, quadrant); + setSubTreeElementCount_(child_handle, + getSubTreeElementCount_(child_handle) + 1); - current_quad_handle = child_handle; - current_extent.setCoords(child_extents[quadrant]); - setSubTreeElementCount_(current_quad_handle, - getSubTreeElementCount_(current_quad_handle) + 1); - continue; + current_quad_handle = child_handle; + current_extent.setCoords(child_extents[i]); + break; + } } - break; + if (!b_contains) + break; } + return insertAtQuad_(element, bounding_box, current_height, + current_extent, current_quad_handle, b_flushing, quad_handle, + flushed_element_handle); + } + + 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 @@ -554,7 +535,7 @@ private int insert_(int element, Envelope2D bounding_box, int height, int head_element_handle = getFirstElement_(current_quad_handle); int tail_element_handle = getLastElement_(current_quad_handle); - int element_handle; + int element_handle = -1; if (b_flushing) { assert (flushed_element_handle != -1); @@ -575,8 +556,6 @@ private int insert_(int element, Envelope2D bounding_box, int height, // (next_element_handle). setBoundingBox_(getBoxHandle_(element_handle), bounding_box); // insert // bounding_box - setSubTreeElementCount_(quad_handle, - getSubTreeElementCount_(quad_handle) + 1); } assert (!b_flushing || element_handle == flushed_element_handle); @@ -650,49 +629,19 @@ private int disconnectElementHandle_(int element_handle) { return next_element_handle; } - private void removeQuad_(int quad_handle) { - int subTreeElementCount = getSubTreeElementCount_(quad_handle); - if (subTreeElementCount > 0) - for (int q = getParent_(quad_handle); q != -1; q = getParent_(q)) - setSubTreeElementCount_(q, getSubTreeElementCount_(q) - - subTreeElementCount); - - removeQuadHelper_(quad_handle); - - int parent = getParent_(quad_handle); - - if (parent != -1) { - for (int quadrant = 0; quadrant < 4; quadrant++) { - if (getChild_(parent, quadrant) == quad_handle) { - setChild_(parent, quadrant, -1); - break; - } - } - } - } - - private void removeQuadHelper_(int quad_handle) { - for (int element_handle = getFirstElement_(quad_handle); element_handle != -1; element_handle = getNextElement_(element_handle)) { - m_free_boxes.add(getBoxHandle_(element_handle)); - m_element_nodes.deleteElement(element_handle); - } - - for (int quadrant = 0; quadrant < 4; quadrant++) { - int child_handle = getChild_(quad_handle, quadrant); - if (child_handle != -1) { - removeQuadHelper_(child_handle); - setChild_(quad_handle, quadrant, -1); - } - } + 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); - if (quad_handle != m_root) { - m_quad_tree_nodes.deleteElement(quad_handle); - } else { - setSubTreeElementCount_(m_root, 0); - setLocalElementCount_(m_root, 0); - setFirstElement_(m_root, -1); - setLastElement_(m_root, -1); - } + 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) { diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 558c3b16..94f187d9 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -100,7 +100,7 @@ public static int rasterSizeFromAccelerationDegree( value = 1024 * 1024 * 2 / 8;// 256k break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } return value; diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index b2ebf125..16e3dd1c 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -95,7 +95,7 @@ void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPat while (segIter.hasNextSegment()) { Segment seg = segIter.nextSegment(); if (seg.getType() != Geometry.Type.Line) - throw new GeometryException("internal error"); // TODO: + throw GeometryException.GeometryInternalError(); // TODO: // densify // the // segment @@ -110,7 +110,7 @@ void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPat } void fillPoints(SimpleRasterizer rasterizer, MultiPointImpl geom, double stroke_half_width) { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 9626b4ca..e88409ff 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1691,8 +1691,9 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, ProgressTracker progress_tracker) { // Quick rasterize test to see whether the the geometries are disjoint. if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) + tolerance, false) == Relation.disjoint) { return false; + } SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) .querySegmentIterator(); @@ -1707,8 +1708,25 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( - (MultiPathImpl) (polyline_a._getImpl()), envInter); + QuadTreeImpl qtA; + QuadTreeImpl quadTreeA; + + GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); @@ -1717,14 +1735,16 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( multipoint_b.getPointCount()); - for (int i = 0; i < multipoint_b.getPointCount(); i++) + for (int i = 0; i < multipoint_b.getPointCount(); i++) { intersects.write(i, (byte) 0); + } for (int i = 0; i < multipoint_b.getPointCount(); i++) { multipoint_b.getXY(i, ptB); - if (!envInter.contains(ptB)) + if (!envInter.contains(ptB)) { continue; + } env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); qtIterA.resetIterator(env_b, tolerance); @@ -1746,16 +1766,18 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, } } - if (!b_intersects) + if (!b_intersects) { return false; + } MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); MultiPoint multipoint_b_inter = new MultiPoint(); Point2D pt = new Point2D(); for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (intersects.read(i) == 0) + if (intersects.read(i) == 0) { continue; + } multipoint_b.getXY(i, pt); multipoint_b_inter.add(pt.x, pt.y); @@ -1771,8 +1793,9 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, ProgressTracker progress_tracker) { // Quick rasterize test to see whether the the geometries are disjoint. if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) + tolerance, false) == Relation.disjoint) { return false; + } SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) .querySegmentIterator(); @@ -1787,8 +1810,25 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( - (MultiPathImpl) polyline_a._getImpl(), envInter); + QuadTreeImpl qtA; + QuadTreeImpl quadTreeA; + + GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); @@ -1798,8 +1838,9 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( multipoint_b.getPointCount()); - for (int i = 0; i < multipoint_b.getPointCount(); i++) + for (int i = 0; i < multipoint_b.getPointCount(); i++) { intersects.write(i, (byte) 0); + } for (int i = 0; i < multipoint_b.getPointCount(); i++) { multipoint_b.getXY(i, ptB); @@ -1831,20 +1872,23 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, } } - if (!b_covered) + if (!b_covered) { b_exterior_found = true; + } } - if (!b_intersects || !b_exterior_found) + if (!b_intersects || !b_exterior_found) { return false; + } MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); MultiPoint multipoint_b_inter = new MultiPoint(); Point2D pt = new Point2D(); for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (intersects.read(i) == 0) + if (intersects.read(i) == 0) { continue; + } multipoint_b.getXY(i, pt); multipoint_b_inter.add(pt.x, pt.y); @@ -2231,7 +2275,7 @@ private static boolean multiPointContainsMultiPointBrute_( } // Returns true if multipoint_a equals point_b. - private static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, + static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, Point point_b, double tolerance, ProgressTracker progress_tracker) { Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); multipoint_a.queryEnvelope2D(env_a); @@ -2598,8 +2642,8 @@ private static boolean pointEqualsEnvelope_(Point2D pt_a, Envelope2D env_b, } // Returns true if pt_a is disjoint from env_b. - private static boolean pointDisjointEnvelope_(Point2D pt_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + static boolean pointDisjointEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { Envelope2D env_b_inflated = new Envelope2D(); env_b_inflated.setCoords(env_b); env_b_inflated.inflate(tolerance, tolerance); @@ -2707,7 +2751,7 @@ private static boolean envelopeEqualsEnvelope_(Envelope2D env_a, } // Returns true if env_a is disjoint from env_b. - private static boolean envelopeDisjointEnvelope_(Envelope2D env_a, + static boolean envelopeDisjointEnvelope_(Envelope2D env_a, Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { Envelope2D env_b_inflated = new Envelope2D(); env_b_inflated.setCoords(env_b); @@ -3019,59 +3063,69 @@ 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 = new Point2D(), pt_b = new Point2D(); + Point2D pt_a, pt_b; Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); - AttributeStreamOfInt32 parts_a = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 parts_b = new AttributeStreamOfInt32(0); + 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 - .getEnvelope2DIntersectorForOGCParts( - (MultiPathImpl) polygon_a._getImpl(), - (MultiPathImpl) multipath_b._getImpl(), tolerance, - parts_a, parts_b); + .getEnvelope2DIntersectorForParts(multi_path_impl_a, + multi_path_impl_b, tolerance, b_simple_a, b_simple_b); if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int path_a = parts_a.get(index_a); - int path_b = parts_b.get(index_b); + if (!intersector.next()) { + return true; // no rings intersect + } + } else { + return true; // no rings intersect + } - multipath_b.getXY(multipath_b.getPathStart(path_b), pt_b); - env_a_inf.setCoords(intersector.getRedEnvelope(index_a)); - env_a_inf.inflate(tolerance, tolerance); + boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, + multipath_b, tolerance); - if (env_a_inf.contains(pt_b)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D(polygon_a, pt_b, 0.0); + if (b_intersects) { + return false; + } - if (result != PolygonUtils.PiPResult.PiPOutside) - 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); - if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { - polygon_a.getXY(polygon_a.getPathStart(path_a), pt_a); - env_b_inf.setCoords(intersector.getBlueEnvelope(index_b)); - env_b_inf.inflate(tolerance, tolerance); + 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_b_inf.contains(pt_a)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D((Polygon) multipath_b, - pt_a, 0.0); + 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 (result != PolygonUtils.PiPResult.PiPOutside) { + return false; } } - } - boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, - multipath_b, tolerance); + 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 (b_intersects) - return false; + 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; } @@ -3386,8 +3440,25 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) .querySegmentIterator(); - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - ((MultiPathImpl) multipathB._getImpl()), envInter); + QuadTreeImpl qtB; + QuadTreeImpl quadTreeB; + + GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeB = accel.getQuadTree(); + if (quadTreeB == null) { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + } else { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); while (segIterA.nextPath()) { @@ -3435,8 +3506,8 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, boolean bSegmentACovered = true; while (bSegmentACovered) {// keep going while the - // current segmentA is - // covered. + // current segmentA is + // covered. if (segIterA.hasNextSegment()) { segmentA = segIterA.nextSegment(); lengthA = segmentA.calculateLength2D(); @@ -3501,16 +3572,17 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, } } - if (bStringOfSegmentAsCovered) + if (bStringOfSegmentAsCovered) { continue; // no need to check that segmentA is covered - + } if (ievent == relOps.m_overlap_events.size()) { return false; // bWithin = false } - if (eventIndices.size() - ievent > 1) + if (eventIndices.size() - ievent > 1) { eventIndices.Sort(ievent, eventIndices.size(), overlapComparer); + } double lastScalar = 0.0; @@ -3519,8 +3591,9 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, .get(i)); if (overlapEvent.m_scalar_a_0 < lastScalar - && overlapEvent.m_scalar_a_1 < lastScalar) + && overlapEvent.m_scalar_a_1 < lastScalar) { continue; + } if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { return false; // bWithin = false @@ -3528,8 +3601,9 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, lastScalar = overlapEvent.m_scalar_a_1; if (lengthA * (1.0 - lastScalar) <= tolerance - || lastScalar == 1.0) + || lastScalar == 1.0) { break; + } } } @@ -3579,115 +3653,6 @@ private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, tolerance); } - // Returns true if the segments of multipathA touches the segments of - // multipathB. - private static boolean linearPathTouchesLinearPath_(MultiPath _multipathA, - MultiPath _multipathB, double tolerance) { - MultiPath multipathA; - MultiPath multipathB; - - if (_multipathA.getSegmentCount() > _multipathB.getSegmentCount()) { - multipathA = _multipathB; - multipathB = _multipathA; - } else { - multipathA = _multipathA; - multipathB = _multipathB; - } - - SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) - .querySegmentIterator(); - SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) - .querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - boolean bBoundaryIntersectionFound = false; - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipathA.queryEnvelope2D(env_a); - multipathB.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); - QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); - - while (segIterA.nextPath()) { - boolean bHasEndPointsA = !isClosedPath_(multipathA, - segIterA.getPathIndex()); - - while (segIterA.hasNextSegment()) { - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env_a); - - if (!env_a.isIntersecting(envInter)) - continue; - - double lengthA = segmentA.calculateLength2D(); - - qtIterB.resetIterator(segmentA, tolerance); - - for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB - .next()) { - int vertex_b = quadTreeB.getElement(elementHandleB); - segIterB.resetToVertex(vertex_b); - boolean bHasEndPointsB = !isClosedPath_(multipathB, - segIterB.getPathIndex()); - - Segment segmentB = segIterB.nextSegment(); - double lengthB = segmentB.calculateLength2D(); - - int result = segmentA.intersect(segmentB, null, scalarsA, - scalarsB, tolerance); - - if (result > 0) { - if (result == 2 - && lengthA * (scalarsA[1] - scalarsA[0]) > tolerance) - return false; - - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - boolean bTouchesStartA = false, bTouchesEndA = false, bTouchesStartB = false, bTouchesEndB = false; - - if (lengthA * scalar_a_0 <= tolerance - && segIterA.isFirstSegmentInPath()) - bTouchesStartA = true; - - if (lengthA * (1.0 - scalar_a_0) <= tolerance - && segIterA.isLastSegmentInPath()) - bTouchesEndA = true; - - if (lengthB * scalar_b_0 <= tolerance - && segIterB.isFirstSegmentInPath()) - bTouchesStartB = true; - - if (lengthB * (1.0 - scalar_b_0) <= tolerance - && segIterB.isLastSegmentInPath()) - bTouchesEndB = true; - - if (((!bTouchesStartA && !bTouchesEndA) || !bHasEndPointsA) - && ((!bTouchesStartB && !bTouchesEndB) || !bHasEndPointsB)) - return false; // return false if Interior-Interior - // intersection is found - - bBoundaryIntersectionFound = true; - } - } - } - } - - return bBoundaryIntersectionFound; // If we haven't already returned - // false, then no Interior-Interior - // intersection has been found. - // Thus, they touch. - } - // Returns true the dimension of intersection of _multipathA and // _multipathB. static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, @@ -3732,11 +3697,29 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, Point2D int_point = null; - if (intersections != null) + if (intersections != null) { int_point = new Point2D(); + } + + QuadTreeImpl qtB; + QuadTreeImpl quadTreeB; + + GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeB = accel.getQuadTree(); + if (quadTreeB == null) { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + } else { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); while (segIterA.nextPath()) { @@ -3746,8 +3729,9 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, Segment segmentA = segIterA.nextSegment(); segmentA.queryEnvelope2D(env_a); - if (!env_a.isIntersecting(envInter)) + if (!env_a.isIntersecting(envInter)) { continue; + } double lengthA = segmentA.calculateLength2D(); @@ -3908,8 +3892,9 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, .get(i)); if (overlapEvent.m_scalar_a_0 < lastScalar - && overlapEvent.m_scalar_a_1 < lastScalar) + && overlapEvent.m_scalar_a_1 < lastScalar) { continue; + } if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { overlapLength = lengthA @@ -3921,10 +3906,10 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, overlapLength = lengthA * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset lastPath = overlapEvent.m_ipath_a; - } else + } else { overlapLength += lengthA * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // accumulate - + } if (overlapLength > tolerance) { dim = 1; return dim; @@ -3932,14 +3917,15 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, lastScalar = overlapEvent.m_scalar_a_1; - if (lastScalar == 1.0) + if (lastScalar == 1.0) { break; + } } } - if (lengthA * (1.0 - lastScalar) > tolerance) + if (lengthA * (1.0 - lastScalar) > tolerance) { overlapLength = 0.0; // reset - + } ievent = 0; eventIndices.resize(0); relOps.m_overlap_events.clear(); @@ -3959,30 +3945,24 @@ private static boolean linearPathIntersectsLinearPath_( SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(multi_path_impl_a, multi_path_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + multi_path_impl_a, multi_path_impl_b, tolerance); - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + 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(); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - int result = segmentB.intersect(segmentA, null, null, null, - tolerance); + int result = segmentB.intersect(segmentA, null, null, null, + tolerance); - if (result > 0) - return true; + if (result > 0) { + return true; } } @@ -4008,15 +3988,33 @@ private static boolean linearPathIntersectsMultiPoint_( multipoint_b.queryEnvelope2D(env_b); env_a.inflate(tolerance, tolerance); - if (!env_a.contains(env_b)) + if (!env_a.contains(env_b)) { bContained = false; + } env_b.inflate(tolerance, tolerance); envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathA._getImpl(), envInter); + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + + GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + quadTreeA = qtA; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; @@ -4025,8 +4023,9 @@ private static boolean linearPathIntersectsMultiPoint_( for (int i = 0; i < multipoint_b.getPointCount(); i++) { multipoint_b.getXY(i, ptB); - if (!envInter.contains(ptB)) + if (!envInter.contains(ptB)) { continue; + } boolean bPtBContained = false; env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); @@ -4050,53 +4049,83 @@ private static boolean linearPathIntersectsMultiPoint_( } if (b_intersects_all) { - if (!b_covered) + if (!b_covered) { return false; + } } else { - if (b_covered) + if (b_covered) { return true; + } } } - if (b_intersects_all) + if (b_intersects_all) { return true; + } return false; } // Returns true if a segment of multipathA intersects point_b. - private static boolean linearPathIntersectsPoint_(MultiPath multipathA, + static boolean linearPathIntersectsPoint_(MultiPath multipathA, Point2D ptB, double tolerance) { - SegmentIterator segIterA = multipathA.querySegmentIterator(); Point2D closest = new Point2D(); double toleranceSq = tolerance * tolerance; + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + + GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) + ._getAccelerators(); + + if (accel != null) { + QuadTreeImpl quadTreeA = accel.getQuadTree(); + if (quadTreeA != null) { + Envelope2D env_b = new Envelope2D(); + env_b.setCoords(ptB); + QuadTreeImpl.QuadTreeIteratorImpl qt_iter = quadTreeA + .getIterator(env_b, tolerance); + + for (int e = qt_iter.next(); e != -1; e = qt_iter.next()) { + segIterA.resetToVertex(quadTreeA.getElement(e)); + + if (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + return true; + } + } + } + + return false; + } + } Envelope2D env_a = new Envelope2D(); - boolean b_intersects = false; while (segIterA.nextPath()) { while (segIterA.hasNextSegment()) { Segment segmentA = segIterA.nextSegment(); - boolean bHasEndPointsA = !isClosedPath_(multipathA, - segIterA.getPathIndex()); - segmentA.queryEnvelope2D(env_a); env_a.inflate(tolerance, tolerance); - if (!env_a.contains(ptB)) + if (!env_a.contains(ptB)) { continue; + } double t = segmentA.getClosestCoordinate(ptB, false); segmentA.getCoord2D(t, closest); if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { - b_intersects = true; - break; + return true; } } } - return b_intersects; + return false; } private static boolean linearPathContainsPoint_(MultiPath multipathA, @@ -4290,18 +4319,6 @@ private static boolean checkVerticesForIntersection_( return false; } - // Returns true if the MultiPath is Closed, or if the end points are within - // a tolerance. - private static boolean isClosedPath_(MultiPath multipathA, int pathA) { - if (multipathA.isClosedPath(pathA)) - return true; - - return false; - // Point2D startA = multipathA.get_xy(multipathA.getPathStart(pathA)); - // Point2D endA = multipathA.get_xy(multipathA.getPathEnd(pathA) - 1); - // return startA.equals(endA); - } - private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); @@ -4313,61 +4330,55 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polygon_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double length_a = segmentA.calculateLength2D(); + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (b_geometries_simple - && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { - // If the line segments overlap along the same - // direction, then we have an Interior-Interior - // intersection - return false; - } + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // If the line segments overlap along the same direction, + // then we have an Interior-Interior intersection + return false; + } - 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; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; + 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; } } - if (!b_boundaries_intersect) + if (!b_boundaries_intersect) { return false; + } Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); polygon_a.queryEnvelope2D(env_a); @@ -4383,8 +4394,9 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4392,14 +4404,14 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, if (polygon_b.getPointCount() > 10) { _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, 0.0)); - if (_polygonB.isEmpty()) + if (_polygonB.isEmpty()) { return false; + } } else { _polygonB = polygon_b; } // We just need to determine whether interior_interior is false - String scl = "F********"; boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( _polygonA, _polygonB, tolerance, scl, progressTracker); @@ -4427,52 +4439,46 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polygon_impl_b, tolerance); - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + 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(); + 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); + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (result == 2) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double length_a = segmentA.calculateLength2D(); + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); - if (b_geometries_simple - && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { - // When the line segments intersect along the same - // direction, then we have an interior-interior - // intersection - bInteriorIntersectionKnown = true; + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // When the line segments intersect along the same + // direction, then we have an interior-interior intersection + bInteriorIntersectionKnown = true; - if (bIntAExtB && bExtAIntB) - return true; + if (bIntAExtB && bExtAIntB) { + return true; } - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + } + } 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 true; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return true; } } } @@ -4490,17 +4496,19 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, Polygon _polygonB; StringBuilder scl = new StringBuilder(); - if (!bInteriorIntersectionKnown) + if (!bInteriorIntersectionKnown) { scl.append("T*"); - else + } else { scl.append("**"); + } if (bIntAExtB) { if (polygon_b.getPointCount() > 10) { _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, 0.0)); - if (_polygonB.isEmpty()) + if (_polygonB.isEmpty()) { return false; + } } else { _polygonB = polygon_b; } @@ -4515,8 +4523,9 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4540,43 +4549,36 @@ private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polygon_impl_b, tolerance); - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + 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(); + 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); + 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 == 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; + 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); @@ -4586,14 +4588,15 @@ private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false + // Exterior-Boundary is false boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( _polygonA, polygon_b, tolerance, scl, progressTracker); @@ -4608,49 +4611,44 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polyline_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + 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); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return false; + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; + 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; } } - if (!b_boundaries_intersect) + if (!b_boundaries_intersect) { return false; + } Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); polygon_a.queryEnvelope2D(env_a); @@ -4666,8 +4664,9 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4675,14 +4674,14 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, if (polyline_b.getPointCount() > 10) { _polylineB = (Polyline) Clipper.clip(polyline_b, envInter, tolerance, 0.0); - if (_polylineB.isEmpty()) + if (_polylineB.isEmpty()) { return false; + } } else { _polylineB = polyline_b; } // We just need to determine that interior_interior is false - String scl = "F********"; boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( _polygonA, _polylineB, tolerance, scl, progressTracker); @@ -4698,50 +4697,44 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); - double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polyline_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + 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); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return true; + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return true; } + + b_boundaries_intersect = true; } } - if (!b_boundaries_intersect) + if (!b_boundaries_intersect) { return false; + } Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envAInflated = new Envelope2D(), envBInflated = new Envelope2D(), envInter = new Envelope2D(); polygon_a.queryEnvelope2D(env_a); @@ -4761,8 +4754,9 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4770,8 +4764,9 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, if (polyline_b.getPointCount() > 10) { _polylineB = (Polyline) (Clipper.clip(polyline_b, envInter, tolerance, 0.0)); - if (_polylineB.isEmpty()) + if (_polylineB.isEmpty()) { return false; + } } else { _polylineB = polyline_b; } @@ -4798,48 +4793,42 @@ private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polyline_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - 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]; + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return false; + 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]; - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) + 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) } } @@ -4853,7 +4842,6 @@ private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, } // 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); @@ -4863,14 +4851,15 @@ private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false + // Exterior-Boundary is false boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( _polygonA, polyline_b, tolerance, scl, progress_tracker); @@ -4899,9 +4888,8 @@ private static boolean polygonTouchesPointImpl_(Polygon polygon_a, return false; } - private static boolean multiPointDisjointPointImpl_( - MultiPoint multipoint_a, Point2D pt_b, double tolerance, - ProgressTracker progressTracker) { + static boolean multiPointDisjointPointImpl_(MultiPoint multipoint_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { Point2D pt_a = new Point2D(); double tolerance_sq = tolerance * tolerance; @@ -4990,4 +4978,205 @@ 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, + Geometry.GeometryAccelerationDegree accel_degree) { + if (!can_accelerate_geometry(geometry)) + return false; + + double tol = InternalUtils.calculateToleranceFromGeometry(sr, + geometry, false); + boolean bAccelerated = false; + if (GeometryAccelerators.canUseRasterizedGeometry(geometry)) + bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildRasterizedGeometryAccelerator(tol, accel_degree); + + Geometry.Type type = geometry.getType(); + if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) + && GeometryAccelerators.canUseQuadTree(geometry) + && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) + bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildQuadTreeAccelerator(accel_degree); + + if (type == Geometry.Type.Polygon + && GeometryAccelerators.canUsePathEnvelopes(geometry) + && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) + bAccelerated |= ((MultiPathImpl) geometry._getImpl()) + ._buildPathEnvelopesAccelerator(accel_degree); + + return bAccelerated; + } + + static boolean can_accelerate_geometry(Geometry geometry) { + return GeometryAccelerators.canUseRasterizedGeometry(geometry) + || GeometryAccelerators.canUseQuadTree(geometry); + } + } } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 7ed7a024..a3b3f02d 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -122,7 +122,7 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, case Geometry.GeometryType.Polygon: bRelation = polygonRelatePolyline_((Polygon) (_geometryB), (Polyline) (_geometryA), tolerance, - transposeMatrix_(scl), progress_tracker); + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Polyline: @@ -151,14 +151,14 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, switch (typeB) { case Geometry.GeometryType.Polygon: bRelation = polygonRelatePoint_((Polygon) (_geometryB), - (Point) (_geometryA), tolerance, transposeMatrix_(scl), - progress_tracker); + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Polyline: bRelation = polylineRelatePoint_((Polyline) (_geometryB), - (Point) (_geometryA), tolerance, transposeMatrix_(scl), - progress_tracker); + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Point: @@ -168,8 +168,8 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, case Geometry.GeometryType.MultiPoint: bRelation = multiPointRelatePoint_((MultiPoint) (_geometryB), - (Point) (_geometryA), tolerance, transposeMatrix_(scl), - progress_tracker); + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); break; default: @@ -182,13 +182,13 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, case Geometry.GeometryType.Polygon: bRelation = polygonRelateMultiPoint_((Polygon) (_geometryB), (MultiPoint) (_geometryA), tolerance, - transposeMatrix_(scl), progress_tracker); + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Polyline: bRelation = polylineRelateMultiPoint_((Polyline) (_geometryB), (MultiPoint) (_geometryA), tolerance, - transposeMatrix_(scl), progress_tracker); + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.MultiPoint: @@ -229,13 +229,47 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, relOps.setPredicates_(scl); relOps.setAreaAreaPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polygon_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); + 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_(); + 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_(); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaAreaContainsPredicates_(); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.within) { + relOps.areaAreaWithinPredicates_(); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -250,18 +284,58 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, relOps.setPredicates_(scl); relOps.setAreaLinePredicates_(); - 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); - relOps.m_cluster_index_b = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_b, relOps.m_topo_graph, relOps.m_cluster_index_b); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_b); - relOps.m_topo_graph.removeShape(); + 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_(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_(polyline_b); // passing + // polyline to + // get boundary + // information + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaLineContainsPredicates_(polyline_b); // passing + // polyline to + // get boundary + // information + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + 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); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_b, relOps.m_topo_graph, + relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -276,13 +350,44 @@ static boolean polygonRelateMultiPoint_(Polygon polygon_a, relOps.setPredicates_(scl); relOps.setAreaPointPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaPointDisjointPredicates_(); + 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, multipoint_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaPointDisjointPredicates_(); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaPointContainsPredicates_(); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -297,23 +402,53 @@ static boolean polylineRelatePolyline_(Polyline polyline_a, relOps.setPredicates_(scl); relOps.setLineLinePredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(polyline_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - relOps.m_cluster_index_b = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); - markClusters_(geom_b, relOps.m_topo_graph, relOps.m_cluster_index_b); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_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.lineLineDisjointPredicates_(polyline_a, polyline_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_(polyline_a, polyline_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_a, relOps.m_topo_graph, + relOps.m_cluster_index_a); + markClusterEndPoints_(geom_b, relOps.m_topo_graph, + relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -328,18 +463,47 @@ static boolean polylineRelateMultiPoint_(Polyline polyline_a, relOps.setPredicates_(scl); relOps.setLinePointPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + 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_(polyline_a, multipoint_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_a, relOps.m_topo_graph, + relOps.m_cluster_index_a); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -354,13 +518,28 @@ static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, relOps.setPredicates_(scl); relOps.setPointPointPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(multipoint_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.pointPointDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(multipoint_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -369,27 +548,44 @@ 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) { - Point2D pt_b = point_b.getXY(); - int[] matrix = new int[9]; - - for (int i = 0; i < 8; i++) - matrix[i] = -1; - - PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, - pt_b, tolerance); + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); - if (res == PolygonUtils.PiPResult.PiPInside) - matrix[MatrixPredicate.InteriorInterior] = 0; - else if (res == PolygonUtils.PiPResult.PiPBoundary) - matrix[MatrixPredicate.BoundaryInterior] = 0; - else - matrix[MatrixPredicate.ExteriorInterior] = 0; + Envelope2D env_a = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); - matrix[MatrixPredicate.InteriorExterior] = 2; - matrix[MatrixPredicate.BoundaryExterior] = 1; - matrix[MatrixPredicate.ExteriorExterior] = 2; + 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_(matrix, scl); + boolean bRelation = relationCompare_(relOps.m_matrix, scl); return bRelation; } @@ -401,21 +597,92 @@ static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, relOps.setPredicates_(scl); relOps.setLinePointPredicates_(); - MultiPoint multipoint_b = new MultiPoint(); - multipoint_b.add(point_b); - - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph.removeShape(); + 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; @@ -425,43 +692,58 @@ static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, static boolean multiPointRelatePoint_(MultiPoint multipoint_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setPointPointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); Point2D pt_b = point_b.getXY(); - int[] matrix = new int[9]; - for (int i = 0; i < 8; i++) - matrix[i] = -1; + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, + env_a, tolerance, progress_tracker); - boolean b_intersects = false; - boolean b_multipoint_contained = true; - double tolerance_sq = tolerance * tolerance; - Point2D pt_a = new Point2D(); + if (b_disjoint) { + relOps.pointPointDisjointPredicates_(); + bRelationKnown = true; + } - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); + if (!bRelationKnown) { + boolean b_intersects = false; + boolean b_multipoint_contained = true; + double tolerance_sq = tolerance * tolerance; - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) { - b_intersects = true; - } else { - b_multipoint_contained = false; + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + Point2D pt_a = multipoint_a.getXY(i); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) + b_intersects = true; + else + b_multipoint_contained = false; + + if (b_intersects && !b_multipoint_contained) + break; } - if (b_intersects && !b_multipoint_contained) - break; - } + if (b_intersects) { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - if (b_intersects) { - matrix[MatrixPredicate.InteriorInterior] = 0; + if (!b_multipoint_contained) + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; + else + relOps.m_matrix[MatrixPredicate.InteriorExterior] = -1; - if (!b_multipoint_contained) - matrix[MatrixPredicate.InteriorExterior] = 0; - } else { - matrix[MatrixPredicate.InteriorExterior] = 0; - matrix[MatrixPredicate.ExteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } } - matrix[MatrixPredicate.ExteriorExterior] = 2; - - boolean bRelation = relationCompare_(matrix, scl); + boolean bRelation = relationCompare_(relOps.m_matrix, scl); return bRelation; } @@ -472,7 +754,7 @@ static boolean pointRelatePoint_(Point point_a, Point point_b, Point2D pt_b = point_b.getXY(); int[] matrix = new int[9]; - for (int i = 1; i < 8; i++) + for (int i = 0; i < 9; i++) matrix[i] = -1; if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) { @@ -521,6 +803,9 @@ private static boolean relationCompare_(int[] matrix, String scl) { if (matrix[i] != 2) return false; break; + + default: + break; } } @@ -684,8 +969,8 @@ private static boolean overlaps_(String scl, int dim_a, int dim_b) { // Marks each cluster of the topoGraph as belonging to an interior vertex of // the geometry and/or a boundary index of the geometry. - private static void markClusters_(int geometry, TopoGraph topoGraph, - int clusterIndex) { + private static void markClusterEndPoints_(int geometry, + TopoGraph topoGraph, int clusterIndex) { EditShape edit_shape = topoGraph.getShape(); for (int path = edit_shape.getFirstPath(geometry); path != -1; path = edit_shape @@ -718,7 +1003,7 @@ private static void markClusters_(int geometry, TopoGraph topoGraph, } } - private static String transposeMatrix_(String scl) { + private static String getTransposeMatrix_(String scl) { String transpose = new String(); transpose += scl.charAt(0); transpose += scl.charAt(3); @@ -743,6 +1028,20 @@ private void resetMatrix_() { m_matrix[i] = -2; } + private void transposeMatrix_() { + int temp1 = m_matrix[1]; + int temp2 = m_matrix[2]; + int temp5 = m_matrix[5]; + + m_matrix[1] = m_matrix[3]; + m_matrix[2] = m_matrix[6]; + m_matrix[5] = m_matrix[7]; + + m_matrix[3] = temp1; + m_matrix[6] = temp2; + m_matrix[7] = temp5; + } + // Sets the relation predicates from the scl string. private void setPredicates_(String scl) { m_scl = scl; @@ -770,6 +1069,9 @@ private void setRemainingPredicatesToFalse_() { 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--; @@ -785,13 +1087,9 @@ private boolean isPredicateKnown_(int predicate, int dim) { return true; } } else { - if (m_matrix[predicate] == -2) { - return false; - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; } } @@ -1018,6 +1316,129 @@ private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { 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 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); + } + } + + private void linePointDisjointPredicates_(Polyline polyline) { + 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); + } + + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + private void pointPointDisjointPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorExterior] = 0; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + // Invokes the 9 relational predicates of area vs Line. private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { boolean bRelationKnown = true; @@ -1792,7 +2213,7 @@ private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { id_a, id_b); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } if (bRelationKnown) @@ -1844,7 +2265,7 @@ private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { bRelationKnown = pointPointPredicates_(cluster, id_a, id_b); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } if (bRelationKnown) @@ -1857,21 +2278,25 @@ private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { // Call this method to set the edit shape, if the edit shape has been // cracked and clustered already. - private void setEditShape_(EditShape shape) { - m_topo_graph.setEditShape(shape, null); + private void setEditShape_(EditShape shape, ProgressTracker progressTracker) { + m_topo_graph.setEditShape(shape, progressTracker); } - // Call this method to set the edit shape, if the edit shape has not been - // cracked and clustered already. private void setEditShapeCrackAndCluster_(EditShape shape, double tolerance, ProgressTracker progress_tracker) { + editShapeCrackAndCluster_(shape, tolerance, progress_tracker); + setEditShape_(shape, progress_tracker); + } + + private void editShapeCrackAndCluster_(EditShape shape, double tolerance, + ProgressTracker progress_tracker) { CrackAndCluster.execute(shape, tolerance, progress_tracker); for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape .getNextGeometry(geometry)) { - if (shape.getGeometryType(geometry) == Geometry.GeometryType.Polygon) - Simplificator.execute(shape, geometry, -1); + if (shape.getGeometryType(geometry) == Geometry.Type.Polygon + .value()) + Simplificator.execute(shape, geometry, -1, false); } - setEditShape_(shape); } // Upgrades the geometry to a feature geometry. diff --git a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java index 21ed6606..970ebe3e 100644 --- a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java +++ b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java @@ -35,6 +35,7 @@ class RingOrientationFixer { int m_node_2_user_index; int m_path_orientation_index; int m_path_parentage_index; + boolean m_fixSelfTangency; static final class Edges { EditShape m_shape; @@ -221,6 +222,11 @@ void reset() { } boolean fixRingOrientation_() { + boolean bFound = false; + + if (m_fixSelfTangency) + bFound = fixRingSelfTangency_(); + if (m_shape.getPathCount(m_geometry) == 1) { int path = m_shape.getFirstPath(m_geometry); double area = m_shape.getRingArea(path); @@ -257,7 +263,6 @@ boolean fixRingOrientation_() { } AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); - boolean bFound = false; m_y_scanline = NumberUtils.TheNaN; Point2D pt = new Point2D(); m_unknown_ring_orientation_count = m_shape.getPathCount(m_geometry); @@ -518,12 +523,163 @@ boolean insertEdge_(int vertex, int reused_node) { } static boolean execute(EditShape shape, int geometry, - IndexMultiDCList sorted_vertices) { + IndexMultiDCList sorted_vertices, boolean fixSelfTangency) { RingOrientationFixer fixer = new RingOrientationFixer(); fixer.m_shape = shape; fixer.m_geometry = geometry; fixer.m_sorted_vertices = sorted_vertices; + fixer.m_fixSelfTangency = fixSelfTangency; return fixer.fixRingOrientation_(); } + boolean fixRingSelfTangency_() { + AttributeStreamOfInt32 self_tangent_paths = new AttributeStreamOfInt32( + 0); + AttributeStreamOfInt32 self_tangency_clusters = new AttributeStreamOfInt32( + 0); + int tangent_path_first_vertex_index = -1; + int tangent_vertex_cluster_index = -1; + Point2D pt_prev = new Point2D(); + pt_prev.setNaN(); + int prev_vertex = -1; + int old_path = -1; + int current_cluster = -1; + Point2D pt = new Point2D(); + for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices + .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices + .getNext(ivertex)) { + int vertex = m_sorted_vertices.getData(ivertex); + m_shape.getXY(vertex, pt); + int path = m_shape.getPathFromVertex(vertex); + if (pt_prev.isEqual(pt) && old_path == path) { + if (tangent_vertex_cluster_index == -1) { + tangent_path_first_vertex_index = m_shape + .createPathUserIndex(); + tangent_vertex_cluster_index = m_shape.createUserIndex(); + } + + if (current_cluster == -1) { + current_cluster = self_tangency_clusters.size(); + m_shape.setUserIndex(prev_vertex, + tangent_vertex_cluster_index, current_cluster); + self_tangency_clusters.add(1); + int p = m_shape.getPathUserIndex(path, + tangent_path_first_vertex_index); + if (p == -1) { + m_shape.setPathUserIndex(path, + tangent_path_first_vertex_index, prev_vertex); + self_tangent_paths.add(path); + } + } + + m_shape.setUserIndex(vertex, tangent_vertex_cluster_index, + current_cluster); + self_tangency_clusters + .setLast(self_tangency_clusters.getLast() + 1); + } else { + current_cluster = -1; + pt_prev.setCoords(pt); + } + + prev_vertex = vertex; + old_path = path; + } + + if (self_tangent_paths.size() == 0) + return false; + + // Now self_tangent_paths contains list of clusters of tangency for each + // path. + // The clusters contains list of clusters and for each cluster it + // contains a list of vertices. + AttributeStreamOfInt32 vertex_stack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 cluster_stack = new AttributeStreamOfInt32(0); + + for (int ipath = 0, npath = self_tangent_paths.size(); ipath < npath; ipath++) { + int path = self_tangent_paths.get(ipath); + int first_vertex = m_shape.getPathUserIndex(path, + tangent_path_first_vertex_index); + int cluster = m_shape.getUserIndex(first_vertex, + tangent_vertex_cluster_index); + vertex_stack.clear(false); + cluster_stack.clear(false); + vertex_stack.add(first_vertex); + cluster_stack.add(cluster); + + for (int vertex = m_shape.getNextVertex(first_vertex); vertex != first_vertex; vertex = m_shape + .getNextVertex(vertex)) { + int vertex_to = vertex; + int cluster_to = m_shape.getUserIndex(vertex_to, + tangent_vertex_cluster_index); + if (cluster_to != -1) { + if (cluster_stack.size() == 0) { + cluster_stack.add(cluster_to); + vertex_stack.add(vertex_to); + continue; + } + + if (cluster_stack.getLast() == cluster_to) { + int vertex_from = vertex_stack.getLast(); + + // peel the loop from path + int from_next = m_shape.getNextVertex(vertex_from); + int from_prev = m_shape.getPrevVertex(vertex_from); + int to_next = m_shape.getNextVertex(vertex_to); + int to_prev = m_shape.getPrevVertex(vertex_to); + + m_shape.setNextVertex_(vertex_from, to_next); + m_shape.setPrevVertex_(to_next, vertex_from); + + m_shape.setNextVertex_(vertex_to, from_next); + m_shape.setPrevVertex_(from_next, vertex_to); + + // vertex_from is left in the path we are processing, + // while the vertex_to is in the loop being teared off. + boolean[] first_vertex_correction_requied = new boolean[] { false }; + int new_path = m_shape.insertClosedPath_(m_geometry, + -1, from_next, m_shape.getFirstVertex(path), + first_vertex_correction_requied); + + m_shape.setUserIndex(vertex, + tangent_vertex_cluster_index, -1); + + // Fix the path after peeling if the peeled loop had the + // first path vertex in it + + if (first_vertex_correction_requied[0]) { + m_shape.setFirstVertex_(path, to_next); + } + + int path_size = m_shape.getPathSize(path); + int new_path_size = m_shape.getPathSize(new_path); + path_size -= new_path_size; + assert (path_size >= 3); + m_shape.setPathSize_(path, path_size); + + self_tangency_clusters.set(cluster_to, + self_tangency_clusters.get(cluster_to) - 1); + if (self_tangency_clusters.get(cluster_to) == 1) { + self_tangency_clusters.set(cluster_to, 0); + cluster_stack.removeLast(); + vertex_stack.removeLast(); + } else { + // this cluster has more than two vertices in it. + } + + first_vertex = vertex_from;// reset the counter to + // ensure we find all loops. + vertex = vertex_from; + } else { + vertex_stack.add(vertex); + cluster_stack.add(cluster_to); + } + } + } + } + + m_shape.removePathUserIndex(tangent_path_first_vertex_index); + m_shape.removeUserIndex(tangent_vertex_cluster_index); + return true; + } + } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index a36ab086..b80a4236 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -748,9 +748,9 @@ int _isIntersecting(Segment other, double tolerance, return Line._isIntersectingLineLine((Line) this, (Line) other, tolerance, bExcludeExactEndpoints); else - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -764,9 +764,9 @@ int _intersect(Segment other, Point2D[] intersectionPoints, return Line._intersectLineLine((Line) this, (Line) other, intersectionPoints, paramThis, paramOther, tolerance); else - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -877,7 +877,9 @@ void _reverseImpl() { abstract boolean _isDegenerate(double tolerance); - abstract double _calculateSubLength(double t); + double _calculateSubLength(double t) { return tToLength(t); } + + double _calculateSubLength(double t1, double t2) { return tToLength(t2) - tToLength(t1); } abstract void _copyToImpl(Segment dst); @@ -917,6 +919,13 @@ abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, * @return X coordinate of the intersection, or NaN, if no intersection. */ abstract double intersectionOfYMonotonicWithAxisX(double y, double xParallel); + + /** + * Converts curves parameter t to the curve length. Can be expensive for curves. + */ + abstract double tToLength(double t); + + abstract double lengthToT(double len); double distance(/* const */Segment otherSegment, boolean bSegmentsKnownDisjoint) /* const */ @@ -963,5 +972,11 @@ && _isIntersecting(otherSegment, 0, false) != 0) { minDistance = distance; return minDistance; - } + } + + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + } diff --git a/src/main/java/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java index 10934a64..17df913d 100644 --- a/src/main/java/com/esri/core/geometry/SegmentBuffer.java +++ b/src/main/java/com/esri/core/geometry/SegmentBuffer.java @@ -55,9 +55,17 @@ public void set(Segment seg) { Line ln = (Line) seg; m_line = ln; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } + + public void create(Geometry.Type type) + { + if (type == Geometry.Type.Line) + createLine(); + else + throw new GeometryException("not implemented"); + } public void createLine() { if (null == m_line) { diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index 794529fd..ba82fd2f 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -195,7 +195,7 @@ public Point getResultPoint() { // Performs the intersection public boolean intersect(double tolerance, boolean b_intersecting) { if (m_input_segments.size() != 2) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); m_tolerance = tolerance; double small_tolerance_sqr = MathUtils.sqr(tolerance * 0.01); @@ -213,7 +213,7 @@ public boolean intersect(double tolerance, boolean b_intersecting) { m_param_1, m_param_2, tolerance); if (count == 0) { assert (count > 0); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } Point2D[] points = new Point2D[9]; for (int i = 0; i < count; i++) { @@ -347,10 +347,10 @@ public boolean intersect(double tolerance, boolean b_intersecting) { return bigmove; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } return false; @@ -360,7 +360,7 @@ public void intersect(double tolerance, Point pt_intersector_point, int point_rank, double point_weight, boolean b_intersecting) { pt_intersector_point.copyTo(m_point); if (m_input_segments.size() != 1) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); m_tolerance = tolerance; @@ -441,7 +441,7 @@ public void intersect(double tolerance, Point pt_intersector_point, return; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index 0d5e1889..b563ccdd 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -115,6 +115,18 @@ public void resetToLastSegment() { m_impl.resetToLastSegment(); } + /** + *Resets the iterator to a specific vertex. + *The call to next_segment will return the segment that starts at the vertex. + *Call to previous_segment will return the segment that starts at the previous vertex. + *@param vertexIndex The vertex index to reset the iterator to. + *@param pathIndex The path index to reset the iterator to. Used as a hint. If the path_index is wrong or -1, then the path_index is automatically calculated. + * + */ + public void resetToVertex(int vertexIndex, int pathIndex) { + m_impl.resetToVertex(vertexIndex, pathIndex); + } + /** * Indicates whether a next segment exists for the path. * diff --git a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java index af1e62b5..c5f97d19 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java @@ -51,6 +51,8 @@ final class SegmentIteratorImpl { protected int m_segmentCount; + protected int m_pathBegin; + protected MultiPathImpl m_parent; // parent of the iterator. protected boolean m_bCirculator; // If true, the iterator circulates around @@ -67,6 +69,7 @@ public SegmentIteratorImpl(MultiPathImpl parent) { m_segmentCount = _getSegmentCount(m_nextPathIndex); m_bCirculator = false; m_currentSegment = null; + m_pathBegin = -1; m_dummyPoint = new Point2D(); } @@ -84,6 +87,7 @@ public SegmentIteratorImpl(MultiPathImpl parent, int pointIndex) { m_segmentCount = _getSegmentCount(m_currentPathIndex); m_bCirculator = false; m_currentSegment = null; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); m_dummyPoint = new Point2D(); } @@ -105,6 +109,7 @@ public SegmentIteratorImpl(MultiPathImpl parent, int pathIndex, m_segmentCount = _getSegmentCount(m_nextPathIndex); m_bCirculator = false; m_currentSegment = null; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); m_dummyPoint = new Point2D(); } @@ -118,6 +123,7 @@ void resetTo(SegmentIteratorImpl src) { m_nextPathIndex = src.m_nextPathIndex; m_segmentCount = src.m_segmentCount; m_bCirculator = src.m_bCirculator; + m_pathBegin = src.m_pathBegin; m_currentSegment = null; } @@ -195,13 +201,17 @@ public void resetToLastSegment() { } public void resetToVertex(int vertexIndex) { + resetToVertex(vertexIndex, -1); + } + + public void resetToVertex(int vertexIndex, int _pathIndex) { if (m_currentPathIndex >= 0 && m_currentPathIndex < m_parent.getPathCount()) {// check if we // are in // the // current // path - int start = m_parent.getPathStart(m_currentPathIndex); + int start = _getPathBegin(); if (vertexIndex >= start && vertexIndex < m_parent.getPathEnd(m_currentPathIndex)) { m_currentSegmentIndex = -1; @@ -210,12 +220,21 @@ public void resetToVertex(int vertexIndex) { } } - int pathIndex = m_parent.getPathIndexFromPointIndex(vertexIndex); - m_nextPathIndex = pathIndex + 1; - m_currentPathIndex = pathIndex; + int path_index; + if (_pathIndex >= 0 && _pathIndex < m_parent.getPathCount() + && vertexIndex >= m_parent.getPathStart(_pathIndex) + && vertexIndex < m_parent.getPathEnd(_pathIndex)) { + path_index = _pathIndex; + } else { + path_index = m_parent.getPathIndexFromPointIndex(vertexIndex); + } + + m_nextPathIndex = path_index + 1; + m_currentPathIndex = path_index; m_currentSegmentIndex = -1; - m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(pathIndex); - m_segmentCount = _getSegmentCount(pathIndex); + m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(path_index); + m_segmentCount = _getSegmentCount(path_index); + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); } /** @@ -231,6 +250,7 @@ public boolean nextPath() { m_currentSegmentIndex = -1; m_nextSegmentIndex = 0; m_segmentCount = _getSegmentCount(m_currentPathIndex); + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); m_nextPathIndex++; return true; } @@ -249,6 +269,7 @@ public boolean previousPath() { m_nextSegmentIndex = 0; m_segmentCount = _getSegmentCount(m_nextPathIndex); m_currentPathIndex = m_nextPathIndex; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); resetToLastSegment(); return true; } @@ -264,6 +285,7 @@ public void resetToFirstPath() { m_segmentCount = -1; m_nextPathIndex = 0; m_currentPathIndex = -1; + m_pathBegin = -1; } /** @@ -276,6 +298,7 @@ public void resetToLastPath() { m_currentSegmentIndex = -1; m_nextSegmentIndex = -1; m_segmentCount = -1; + m_pathBegin = -1; } /** @@ -293,6 +316,7 @@ public void resetToPath(int pathIndex) { m_currentSegmentIndex = -1; m_nextSegmentIndex = -1; m_segmentCount = -1; + m_pathBegin = -1; } public int _getSegmentCount(int pathIndex) { @@ -416,13 +440,13 @@ public void _updateSegment() { m_currentSegment = (Line) m_line; break; case SegmentFlags.enumBezierSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } m_currentSegment.assignVertexDescription(vertexDescr); diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 88760528..5bfaf014 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -41,6 +41,7 @@ class Simplificator { private int m_firstCoincidentVertex; private int m_knownSimpleResult; private boolean m_bWinding; + private boolean m_fixSelfTangency; private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, @@ -525,7 +526,7 @@ private boolean _simplify() { } if (iRepeatNum++ > 10) { - throw new GeometryException("internal error."); + throw GeometryException.GeometryInternalError(); } if (bNeedRepeat) @@ -549,7 +550,7 @@ private boolean _simplify() { m_shape.removeUserIndex(m_userIndexSortedAngleIndexToVertex); bChanged |= RingOrientationFixer.execute(m_shape, m_geometry, - m_sortedVertices); + m_sortedVertices, m_fixSelfTangency); return bChanged; } @@ -984,12 +985,13 @@ protected Simplificator() { } public static boolean execute(EditShape shape, int geometry, - int knownSimpleResult) { + int knownSimpleResult, boolean fixSelfTangency) { Simplificator simplificator = new Simplificator(); simplificator.m_shape = shape; // simplificator.m_bWinding = bWinding; simplificator.m_geometry = geometry; simplificator.m_knownSimpleResult = knownSimpleResult; + simplificator.m_fixSelfTangency = fixSelfTangency; return simplificator._simplify(); } @@ -999,6 +1001,11 @@ int _compareVerticesSimple(int v1, int v2) { Point2D pt2 = new Point2D(); m_shape.getXY(v2, pt2); int res = pt1.compare(pt2); + if (res == 0) {// sort equal vertices by the path ID + res = Integer.compare(m_shape.getPathFromVertex(v1), + m_shape.getPathFromVertex(v2)); + } + return res; } diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 6a7540a5..081b2b64 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -41,9 +41,9 @@ import com.esri.core.geometry.VertexDescription.Semantics; class SpatialReferenceImpl extends SpatialReference { - static boolean no_projection_engine = true; - public static int c_SULIMIT32 = 2147483645; - public static long c_SULIMIT64 = 9007199254740990L; + static final boolean no_projection_engine = true; + public final static int c_SULIMIT32 = 2147483645; + public final static long c_SULIMIT64 = 9007199254740990L; enum Precision { Integer32, Integer64, FloatingPoint @@ -126,7 +126,7 @@ public void queryValidCoordinateRange(Envelope2D env2D) { break; default: // TODO - throw new GeometryException("internal error");// fixme + throw GeometryException.GeometryInternalError(); } env2D.setCoords(getFalseX(), getFalseY(), getFalseX() + delta, diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index ca631d98..93de656f 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -23,8 +23,6 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; - /** * 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. @@ -32,31 +30,37 @@ * time creation and deletion of an element. */ final class StridedIndexTypeCollection { - - private int m_firstFree; - private int m_last; + private int[][] m_buffer = null; + private int m_firstFree = -1; + private int m_last = 0; + private int m_size = 0; + private int m_capacity = 0; + private int m_bufferSize = 0; private int m_stride; private int m_realStride; - private int m_realBlockSize; private int m_blockSize; - private int m_blockMask; - private int m_blockPower; - private int m_size; - private int m_capacity; - private ArrayList m_buffer; + + /* + private final static int m_realBlockSize = 2048;//if you change this, change m_blockSize, m_blockPower, m_blockMask, and st_sizes + private final static int m_blockMask = 0x7FF; + private final static int m_blockPower = 11; + private final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; + */ + + private final static int m_realBlockSize = 16384;// if you change this, + // change m_blockSize, + // m_blockPower, + // m_blockMask, and + // st_sizes + private final static int m_blockMask = 0x3FFF; + private final static int m_blockPower = 14; + private final static int[] st_sizes = { 16, 32, 64, 128, 256, 512, 1024, + 2048, 4096, 8192, 16384 }; StridedIndexTypeCollection(int stride) { - m_firstFree = -1; - m_last = 0; - m_size = 0; m_stride = stride; m_realStride = stride; - m_realBlockSize = 2048;//if you change this, change m_blockSize, m_blockPower, m_blockMask, and st_sizes - m_blockPower = 11; - m_blockMask = 0x7FF; - m_blockSize = 2048 / m_realStride; - m_capacity = 0; - m_buffer = null; + m_blockSize = m_realBlockSize / m_realStride; } void deleteElement(int element) { @@ -64,7 +68,7 @@ void deleteElement(int element) { int totalStrides = (element >> m_blockPower) * m_blockSize * m_realStride + (element & m_blockMask); if (totalStrides < m_last * m_realStride) { - m_buffer.get(element >> m_blockPower)[element & m_blockMask] = m_firstFree; + m_buffer[element >> m_blockPower][element & m_blockMask] = m_firstFree; m_firstFree = element; } else { assert (totalStrides == m_last * m_realStride); @@ -75,13 +79,13 @@ void deleteElement(int element) { // Returns the given field of the element. int getField(int element, int field) { - return m_buffer.get(element >> m_blockPower)[(element & m_blockMask) + return m_buffer[element >> m_blockPower][(element & m_blockMask) + field]; } // Sets the given field of the element. void setField(int element, int field, int value) { - m_buffer.get(element >> m_blockPower)[(element & m_blockMask) + field] = value; + m_buffer[element >> m_blockPower][(element & m_blockMask) + field] = value; } // Returns the stride size @@ -95,13 +99,15 @@ int newElement() { int element = m_firstFree; if (element == -1) { if (m_last == m_capacity) { - long newcap = m_capacity != 0 ? (((long)m_capacity + 1) * 3 / 2) : (long)1; + long newcap = m_capacity != 0 ? (((long) m_capacity + 1) * 3 / 2) + : (long) 1; if (newcap > Integer.MAX_VALUE) - newcap = Integer.MAX_VALUE;//cannot grow past 2gb elements presently - + newcap = Integer.MAX_VALUE;// cannot grow past 2gb elements + // presently + if (newcap == m_capacity) throw new IndexOutOfBoundsException(); - + grow_(newcap); } @@ -109,12 +115,12 @@ int newElement() { + (m_last % m_blockSize) * m_realStride; m_last++; } else { - m_firstFree = m_buffer.get(element >> m_blockPower)[element + m_firstFree = m_buffer[element >> m_blockPower][element & m_blockMask]; } m_size++; - int ar[] = m_buffer.get(element >> m_blockPower); + int ar[] = m_buffer[element >> m_blockPower]; int ind = element & m_blockMask; for (int i = 0; i < m_stride; i++) { ar[ind + i] = -1; @@ -157,8 +163,8 @@ int capacity() { // Swaps content of two elements (each field of the stride) void swap(int element1, int element2) { - int ar1[] = m_buffer.get(element1 >> m_blockPower); - int ar2[] = m_buffer.get(element2 >> m_blockPower); + int ar1[] = m_buffer[element1 >> m_blockPower]; + int ar2[] = m_buffer[element2 >> m_blockPower]; int ind1 = element1 & m_blockMask; int ind2 = element2 & m_blockMask; for (int i = 0; i < m_stride; i++) { @@ -170,8 +176,8 @@ void swap(int element1, int element2) { // Swaps content of two fields void swapField(int element1, int element2, int field) { - int ar1[] = m_buffer.get(element1 >> m_blockPower); - int ar2[] = m_buffer.get(element2 >> m_blockPower); + int ar1[] = m_buffer[element1 >> m_blockPower]; + int ar2[] = m_buffer[element2 >> m_blockPower]; int ind1 = (element1 & m_blockMask) + field; int ind2 = (element2 & m_blockMask) + field; int tmp = ar1[ind1]; @@ -200,11 +206,21 @@ private boolean dbgdelete_(int element) { return true; } - final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; + private void ensureBufferBlocksCapacity(int blocks) { + if (m_buffer.length < blocks) { + int[][] newBuffer = new int[blocks][]; + for (int i = 0; i < m_buffer.length; i++) { + newBuffer[i] = m_buffer[i]; + } + + m_buffer = newBuffer; + } + } private void grow_(long newsize) { if (m_buffer == null) { - m_buffer = new ArrayList(); + m_bufferSize = 0; + m_buffer = new int[8][]; } assert (newsize > m_capacity); @@ -212,49 +228,47 @@ private void grow_(long newsize) { long nblocks = (newsize + m_blockSize - 1) / m_blockSize; if (nblocks > Integer.MAX_VALUE) throw new IndexOutOfBoundsException(); - - m_buffer.ensureCapacity((int)nblocks); + + ensureBufferBlocksCapacity((int) nblocks); if (nblocks == 1) { // When less than one block is needed we allocate smaller arrays // than m_realBlockSize to avoid initialization cost. int oldsz = m_capacity > 0 ? m_capacity : 0; assert (oldsz < newsize); int i = 0; - int realnewsize = (int)newsize * m_realStride; + int realnewsize = (int) newsize * m_realStride; while (realnewsize > st_sizes[i]) // get the size to allocate. Using fixed sizes to reduce // fragmentation. i++; int[] b = new int[st_sizes[i]]; - if (m_buffer.size() == 1) { - System.arraycopy(m_buffer.get(0), 0, b, 0, m_size - * m_realStride); - m_buffer.set(0, b); + if (m_bufferSize == 1) { + System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); + m_buffer[0] = b; } else { - m_buffer.add(b); + m_buffer[m_bufferSize] = b; + m_bufferSize++; } m_capacity = b.length / m_realStride; } else { if (nblocks * m_blockSize > Integer.MAX_VALUE) throw new IndexOutOfBoundsException(); - - if (m_buffer.size() == 1) { - if (m_buffer.get(0).length < m_realBlockSize) { + + if (m_bufferSize == 1) { + if (m_buffer[0].length < m_realBlockSize) { // resize the first buffer to ensure it is equal the // m_realBlockSize. int[] b = new int[m_realBlockSize]; - System.arraycopy(m_buffer.get(0), 0, b, 0, m_size - * m_realStride); - m_buffer.set(0, b); + System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); + m_buffer[0] = b; m_capacity = m_blockSize; } } - while (m_buffer.size() < nblocks) { - m_buffer.add(new int[m_realBlockSize]); + while (m_bufferSize < nblocks) { + m_buffer[m_bufferSize++] = new int[m_realBlockSize]; m_capacity += m_blockSize; } } } - } diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index e7a40cfa..affcbc60 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -34,7 +34,7 @@ static interface EnumInputMode { final static int enumInputModeBuildGraph = 0; final static int enumInputModeSimplifyAlternate = 4 + 0; final static int enumInputModeSimplifyWinding = 4 + 1; - final static int enumInputModeSimplifyForBuffer = 4 + 2; + final static int enumInputModeIsSimplePolygon = 4 + 3; } EditShape m_shape; @@ -64,6 +64,13 @@ static interface EnumInputMode { int m_halfEdgeIndex; // vertex index of half-edges in the m_shape int m_tmpHalfEdgeParentageIndex; int m_tmpHalfEdgeWindingNumberIndex; + int m_tmpHalfEdgeOddEvenNumberIndex = -1; + + int m_universe_geomID = -1; + + boolean m_buildChains = true; + + NonSimpleResult m_non_simple_result = new NonSimpleResult(); int m_pointCount;// point count processed in this Topo_graph. Used to // reserve data. @@ -554,15 +561,6 @@ void planeSweepParentage_(int inputMode, ProgressTracker progress_tracker) { int attachedTreeNode = getHalfEdgeUserIndex( clusterHalfEdge, treeNodeIndex); if (attachedTreeNode == -1) { - // #ifdef DEBUG - // //Debug-checking that the "from" is below the - // "to" - // Point_2D pt_1; - // getHalfEdgeFromXY(clusterHalfEdge, pt_1); - // Point_2D pt_2; - // getHalfEdgeToXY(clusterHalfEdge, pt_2); - // assert(pt_1.compare(pt_2) < 0); - // #endif int newTreeNode = aet.addElement(clusterHalfEdge, -1); new_edges.add(newTreeNode); @@ -701,160 +699,214 @@ void planeSweepParentagePropagateParentage_(Treap aet, int treeNode, assert (parentChain != -1); - // Now do specific sweep calculations if (inputMode == EnumInputMode.enumInputModeBuildGraph) { - int chainParentage = getChainParentage(edgeChain); + propagate_parentage_build_graph_(aet, treeNode, edge, leftEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + propagate_parentage_winding_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + propagate_parentage_alternate_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + + } + + void propagate_parentage_build_graph_(Treap aet, int treeNode, int edge, int leftEdge, + int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { + // Now do specific sweep calculations + int chainParentage = getChainParentage(edgeChain); - if (leftEdge != -1) { - // borrow the parentage from the left edge also - int leftEdgeChain = getHalfEdgeChain(leftEdge); - // dbg_print_edge_(edge); - // dbg_print_edge_(leftEdge); - - // We take parentage from the left edge (that edge has been - // already processed), and move its face parentage accross this - // edge/twin pair. - // While the parentage is moved, accross, any bits of the - // parentage that is present in the twin are removed, because - // the twin is the right edge of the current face. - // The remaining bits are added to the face parentage of this - // edge, indicating that the face this edge borders, belongs to - // all the parents that are still active to the left. - int twinChainParentage = getChainParentage(twinHalfEdgeChain); - int leftChainParentage = getChainParentage(leftEdgeChain); - - int edgeParentage = getHalfEdgeParentage(edge); - int spikeParentage = chainParentage & twinChainParentage - & leftChainParentage; // parentage that needs to stay - leftChainParentage = leftChainParentage + if (leftEdge != -1) { + // borrow the parentage from the left edge also + int leftEdgeChain = getHalfEdgeChain(leftEdge); + + // We take parentage from the left edge (that edge has been + // already processed), and move its face parentage accross this + // edge/twin pair. + // While the parentage is moved, accross, any bits of the + // parentage that is present in the twin are removed, because + // the twin is the right edge of the current face. + // The remaining bits are added to the face parentage of this + // edge, indicating that the face this edge borders, belongs to + // all the parents that are still active to the left. + int twinChainParentage = getChainParentage(twinHalfEdgeChain); + int leftChainParentage = getChainParentage(leftEdgeChain); + + int edgeParentage = getHalfEdgeParentage(edge); + int spikeParentage = chainParentage & twinChainParentage + & leftChainParentage; // parentage that needs to stay + leftChainParentage = leftChainParentage ^ (leftChainParentage & edgeParentage); - leftChainParentage |= spikeParentage; - // leftChainParentage = leftChainParentage ^ (leftChainParentage - // & twinChainParentage);//only parentage that is abscent in the - // twin is propagated to the right - // leftChainParentage = leftChainParentage ^ (leftChainParentage - // & edgeParentage);//only parentage that is abscent in the twin - // is propagated to the right - // & (and) leaves the parentage that is common for left edge and - // the twin, while ^ (xor) leaves the parentage that is present - // in the - // left edge only. - - if (leftChainParentage != 0) { - // propagate left parentage to the current edge and its - // twin. - setChainParentage_(twinHalfEdgeChain, twinChainParentage + leftChainParentage |= spikeParentage; + + if (leftChainParentage != 0) { + // propagate left parentage to the current edge and its + // twin. + setChainParentage_(twinHalfEdgeChain, twinChainParentage | leftChainParentage); - setChainParentage_(edgeChain, leftChainParentage + setChainParentage_(edgeChain, leftChainParentage | chainParentage); - chainParentage |= leftChainParentage; - } + chainParentage |= leftChainParentage; + } // dbg_print_edge_(edge); - } + } - for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet + for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet .getNext(rightNode)) { - int rightEdge = aet.getElement(rightNode); - int rightTwin = getHalfEdgeTwin(rightEdge); - // dbg_print_edge_(rightEdge); - int rightTwinChain = getHalfEdgeChain(rightTwin); - int rightTwinChainParentage = getChainParentage(rightTwinChain); - int rightEdgeParentage = getHalfEdgeParentage(rightEdge); - int rightEdgeChain = getHalfEdgeChain(rightEdge); - int rightChainParentage = getChainParentage(rightEdgeChain); - - int spikeParentage = rightTwinChainParentage - & rightChainParentage & chainParentage; // parentage - // that needs to - // stay - chainParentage = chainParentage - ^ (chainParentage & rightEdgeParentage);// only - // parentage - // that is - // abscent in - // the twin is - // propagated to - // the right - chainParentage |= spikeParentage; - - if (chainParentage == 0) - break; - - setChainParentage_(rightTwinChain, rightTwinChainParentage + int rightEdge = aet.getElement(rightNode); + int rightTwin = getHalfEdgeTwin(rightEdge); + + int rightTwinChain = getHalfEdgeChain(rightTwin); + int rightTwinChainParentage = getChainParentage(rightTwinChain); + int rightEdgeParentage = getHalfEdgeParentage(rightEdge); + int rightEdgeChain = getHalfEdgeChain(rightEdge); + int rightChainParentage = getChainParentage(rightEdgeChain); + + int spikeParentage = rightTwinChainParentage + & rightChainParentage & chainParentage; // parentage + // that needs to + // stay + chainParentage = chainParentage + ^ (chainParentage & rightEdgeParentage);// only + // parentage + // that is + // abscent in + // the twin is + // propagated to + // the right + chainParentage |= spikeParentage; + + if (chainParentage == 0) + break; + + setChainParentage_(rightTwinChain, rightTwinChainParentage | chainParentage); - setChainParentage_(rightEdgeChain, rightChainParentage + setChainParentage_(rightEdgeChain, rightChainParentage | chainParentage); - // dbg_print_edge_(rightEdge); - } } + } - if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { - if (edgeChain == twinHalfEdgeChain) - return; - // starting from the left most edge, calculate winding. - int edgeWinding = getHalfEdgeUserIndex(edge, - m_tmpHalfEdgeWindingNumberIndex); - edgeWinding += getHalfEdgeUserIndex(twinEdge, - m_tmpHalfEdgeWindingNumberIndex); - int winding = 0; - AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); - windingStack.add(0); - for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet + void propagate_parentage_winding_(Treap aet, int treeNode, int edge, int leftEdge, int twinEdge, + int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { + + if (edgeChain == twinHalfEdgeChain) + return; + // starting from the left most edge, calculate winding. + int edgeWinding = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeWindingNumberIndex); + edgeWinding += getHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeWindingNumberIndex); + int winding = 0; + AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); + windingStack.add(0); + for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet .getNext(leftNode)) { - int leftEdge1 = aet.getElement(leftNode); - int leftTwin = getHalfEdgeTwin(leftEdge1); - int l_chain = getHalfEdgeChain(leftEdge1); - int lt_chain = getHalfEdgeChain(leftTwin); - - if (l_chain != lt_chain) { - int leftWinding = getHalfEdgeUserIndex(leftEdge1, - m_tmpHalfEdgeWindingNumberIndex); - leftWinding += getHalfEdgeUserIndex(leftTwin, - m_tmpHalfEdgeWindingNumberIndex); - winding += leftWinding; - - boolean popped = false; - if (chainStack.size() != 0 - && chainStack.getLast() == lt_chain) { - windingStack - .resizePreserveCapacity(windingStack.size() - 1); - chainStack - .resizePreserveCapacity(chainStack.size() - 1); - popped = true; - } + int leftEdge1 = aet.getElement(leftNode); + int leftTwin = getHalfEdgeTwin(leftEdge1); + int l_chain = getHalfEdgeChain(leftEdge1); + int lt_chain = getHalfEdgeChain(leftTwin); + + if (l_chain != lt_chain) { + int leftWinding = getHalfEdgeUserIndex(leftEdge1, + m_tmpHalfEdgeWindingNumberIndex); + leftWinding += getHalfEdgeUserIndex(leftTwin, + m_tmpHalfEdgeWindingNumberIndex); + winding += leftWinding; + + boolean popped = false; + if (chainStack.size() != 0 + && chainStack.getLast() == lt_chain) { + windingStack.removeLast(); + chainStack.removeLast(); + popped = true; + } - // geometry_release_assert(getChainParent(lt_chain) != -1); + if (getChainParent(lt_chain) == -1) + throw GeometryException.GeometryInternalError(); - if (!popped || getChainParent(lt_chain) != l_chain) { - windingStack.add(winding); - chainStack.add(l_chain); - } + if (!popped || getChainParent(lt_chain) != l_chain) { + windingStack.add(winding); + chainStack.add(l_chain); } } + } - winding += edgeWinding; + winding += edgeWinding; - if (chainStack.size() != 0 + if (chainStack.size() != 0 && chainStack.getLast() == twinHalfEdgeChain) { - windingStack.resizePreserveCapacity(windingStack.size() - 1); - chainStack.resizePreserveCapacity(chainStack.size() - 1); + windingStack.removeLast(); + chainStack.removeLast(); + } + + if (winding != 0) { + if (windingStack.getLast() == 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); } + } else { + if (windingStack.getLast() != 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); + } + } + } + + void propagate_parentage_alternate_(Treap aet, int treeNode, int edge, + int leftEdge, int twinEdge, int edgeChain, int edgeChainParent, + int twinHalfEdgeChain) { + // Now do specific sweep calculations + // This one is done when we are doing a topological operation. + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + + if (leftEdge == -1) { + // no left edge neighbour means the twin chain is surrounded by the + // universe + assert (getChainParent(twinHalfEdgeChain) == m_universeChain); + assert (getChainParentage(twinHalfEdgeChain) == 0 || getChainParentage(twinHalfEdgeChain) == m_universe_geomID); + assert (getChainParentage(edgeChain) == 0); + setChainParentage_(twinHalfEdgeChain, m_universe_geomID); + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, geometryID);// set the parenentage + // from the parity + else + setChainParentage_(edgeChain, m_universe_geomID);// this chain + // does not + // belong to + // geometry + } else { + int twin_parentage = getChainParentage(twinHalfEdgeChain); + if (twin_parentage == 0) { + int leftEdgeChain = getHalfEdgeChain(leftEdge); + int left_parentage = getChainParentage(leftEdgeChain); + setChainParentage_(twinHalfEdgeChain, left_parentage); + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, + (left_parentage == geometryID) ? m_universe_geomID + : geometryID); + else + setChainParentage_(edgeChain, left_parentage); - if (winding != 0) { - if (windingStack.read(windingStack.size() - 1) == 0) { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - setChainParentage_(edgeChain, geometryID); - } } else { - if (windingStack.read(windingStack.size() - 1) != 0) { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - setChainParentage_(edgeChain, geometryID); - } + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, + (twin_parentage == geometryID) ? m_universe_geomID + : geometryID); + else + setChainParentage_(edgeChain, twin_parentage); } + } } @@ -901,13 +953,10 @@ boolean trySetChainParentFromTwin_(int chainToSet, int twinChain) { double twinArea = getChainArea(twinChain); assert (twinArea != 0); if (area > 0 && twinArea < 0) { - // assert(twinArea + area <= area * Number_utils::double_eps() * - // 100); setChainParent_(chainToSet, twinChain); return true; } if (area < 0 && twinArea > 0) { - // assert(-area <= twinArea); setChainParent_(chainToSet, twinChain); return true; } else { @@ -1056,7 +1105,24 @@ void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { m_tmpHalfEdgeWindingNumberIndex, windingNumber); setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeWindingNumberIndex, windingNumberTwin); + } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + m_universe_geomID); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, + gt == Geometry.GeometryType.Polygon ? geometryID + : 0); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeOddEvenNumberIndex, 1); + setHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeOddEvenNumberIndex, 1); } + int edgeBit = gt == Geometry.GeometryType.Polygon ? c_edgeBitMask : 0; setHalfEdgeParentage_(half_edge, geometryID | edgeBit); @@ -1095,12 +1161,13 @@ void mergeVertexListsOfEdges_(int eDst, int eSrc) { void sortHalfEdgesByAngle_(int inputMode) { AttributeStreamOfInt32 angleSorter = new AttributeStreamOfInt32(0); angleSorter.reserve(10); + TopoGraphAngleComparer tgac = new TopoGraphAngleComparer(this); // Now go through the clusters, sort edges in each cluster by angle, and // reconnect the halfedges of sorted edges in the sorted order. // Also share the parentage information between coinciding edges and // remove duplicates. for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - angleSorter.resizePreserveCapacity(0); + angleSorter.clear(false); int first = getClusterHalfEdge(cluster); if (first != -1) { // 1. sort edges originating at the cluster by angle (counter - @@ -1117,7 +1184,7 @@ void sortHalfEdgesByAngle_(int inputMode) { if (angleSorter.size() > 1) { if (angleSorter.size() > 2) { angleSorter.Sort(0, angleSorter.size(), - new TopoGraphAngleComparer(this)); // std::sort(angleSorter.get_ptr(), + tgac); // std::sort(angleSorter.get_ptr(), // angleSorter.get_ptr() // + // angleSorter.size(), @@ -1224,12 +1291,31 @@ void sortHalfEdgesByAngle_(int inputMode) { // even number of edges coinciding there and // half of them has opposite direction to // another half. + } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); + return; + } + else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { + int newHalfEdgeWinding = getHalfEdgeUserIndex( + ePrev, m_tmpHalfEdgeOddEvenNumberIndex) + + getHalfEdgeUserIndex(e, + m_tmpHalfEdgeOddEvenNumberIndex); + int newTwinEdgeWinding = getHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeOddEvenNumberIndex) + + getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeOddEvenNumberIndex); + setHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeOddEvenNumberIndex, + newHalfEdgeWinding); + setHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeOddEvenNumberIndex, + newTwinEdgeWinding); } mergeVertexListsOfEdges_(ePrev, e); deleteEdgeImpl_(e); - assert (n < 3 || e0 == angleSorter.read(angleSorter - .size() - 1)); + assert (n < 3 || e0 == angleSorter.getLast()); prevMerged = ePrev; angleSorter.set(i, -1); if (e == e0) { @@ -1284,6 +1370,18 @@ void sortHalfEdgesByAngle_(int inputMode) { ePrev = e; ePrevTo = eTo; ePrevTwin = eTwin; + if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) + { + int par1 = getHalfEdgeUserIndex(e, m_tmpHalfEdgeParentageIndex) | + getHalfEdgeUserIndex(getHalfEdgePrev(e), m_tmpHalfEdgeParentageIndex); + if (par1 == (m_universe_geomID | 1)) + { + //violation of face parentage + m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); + return; + } + } + } setClusterHalfEdge_(cluster, e0);// smallest angle goes @@ -1293,7 +1391,7 @@ void sortHalfEdgesByAngle_(int inputMode) { } } - void buildChains_() { + void buildChains_(int inputMode) { // Creates chains and puts them in the list of chains. // Does not set the chain parentage // Does not connect chains @@ -1347,15 +1445,10 @@ void buildChains_() { // visited. e = getHalfEdgeNext(e); } while (e != edge); + + assert(inputMode != EnumInputMode.enumInputModeIsSimplePolygon || parentage != (1 | m_universe_geomID)); + setChainParentage_(chain, parentage); - - // Polygon::SPtr poly = - // dbg_dump_chain_to_polygon_(chain); - // char buf[1024]; - // sprintf(buf, "c:\\temp\\chain%d.txt", - // m_chain_data->size()); - // Operator_factory_local::SaveGeometryToInternalTextFile(buf, - // poly); } edge = getHalfEdgeNext(getHalfEdgeTwin(edge));// next @@ -1394,7 +1487,6 @@ void buildChains_() { } void simplify_(int inputMode) { - assert (inputMode != EnumInputMode.enumInputModeBuildGraph); if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { simplifyAlternate_(); } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { @@ -1403,80 +1495,77 @@ void simplify_(int inputMode) { } void simplifyAlternate_() { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - - int universe_chain = getFirstChain(); // winding = 0; - simplifyAlternateRecursion_(universe_chain, geometryID, false); - } - - void simplifyAlternateRecursion_(int parent_chain, int geometryID, - boolean bEven) { - boolean bEven_ = !bEven; - for (int chain = getChainFirstIsland(parent_chain); chain != -1; chain = getChainNextInParent(chain)) { - if (bEven_) - setChainParentage_(chain, geometryID); - else - setChainParentage_(chain, 0); - - simplifyAlternateRecursion_(chain, geometryID, bEven_); - } + //there is nothing to do } void simplifyWinding_() { - // there is nothing to do. + //there is nothing to do } boolean removeSpikes_() { - boolean bRemoved = false; - for (int chain = getFirstChain(); chain != -1;) { - int firstHalfEdge = getChainHalfEdge(chain); - assert (firstHalfEdge != -1); - - boolean bRemovedEdge = false; + boolean removed = false; + int visitedIndex = createUserIndexForHalfEdges(); + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + int firstHalfEdge = getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; - int prev = getHalfEdgePrev(firstHalfEdge); int half_edge = firstHalfEdge; - while (true) { - int twin = getHalfEdgeTwin(half_edge); - if (prev == twin) { - int next2 = deleteEdgeInternal_(half_edge); - bRemovedEdge = true; - if (next2 == -1) { - // The chain has been deleted. - break; - } - if (half_edge == firstHalfEdge) { - firstHalfEdge = next2; - } + do { + int visited = getHalfEdgeUserIndex(half_edge, visitedIndex); + int halfEdgeNext = getHalfEdgeNext(getHalfEdgeTwin(half_edge)); + if (visited != 1) { + int faceHalfEdge = half_edge; + do { + int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); + int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); + int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + // We first check whether faceHalfEdge corresponds to + // the starting segment with + // "faceHalfEdge != half_edge && faceHalfEdgePrev != half_edge" + // Otherwise if we deleted the first half edge, then + // there could be an infinite loop since the terminal + // condition wouldn't occur. + // if (faceHalfEdge != half_edge && faceHalfEdgeNext != + // half_edge && faceHalfEdgePrev == faceHalfEdgeTwin) + if (faceHalfEdge != half_edge + && faceHalfEdgePrev != half_edge + && faceHalfEdgePrev == faceHalfEdgeTwin) { + deleteEdgeInternal_(faceHalfEdge); + removed = true; + assert (faceHalfEdgeNext != faceHalfEdge); + assert (faceHalfEdgeNext != faceHalfEdgeTwin); + assert (faceHalfEdgeNext != faceHalfEdgePrev); + } else { + setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); + } - half_edge = next2; - prev = getHalfEdgePrev(half_edge); - } else { - prev = half_edge; - half_edge = getHalfEdgeNext(half_edge); - if (half_edge == firstHalfEdge) - break; + faceHalfEdge = faceHalfEdgeNext; + } while (faceHalfEdge != half_edge); + + int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); + int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + if (faceHalfEdgePrev == faceHalfEdgeTwin) { + deleteEdgeInternal_(faceHalfEdge); + removed = true; + } } - } - bRemoved |= bRemovedEdge; - if (bRemovedEdge) { - chain = deleteChain_(chain); - } else - chain = getChainNext(chain); + half_edge = halfEdgeNext; + } while (half_edge != firstHalfEdge); } - return bRemoved; + return removed; } - + void setEditShapeImpl_(EditShape shape, int inputMode, AttributeStreamOfInt32 editShapeGeometries, - ProgressTracker progress_tracker) { + ProgressTracker progress_tracker, boolean bBuildChains) { assert (editShapeGeometries == null || editShapeGeometries.size() > 0); removeShape(); + m_buildChains = bBuildChains; assert (m_shape == null); m_shape = shape; m_geometryIDIndex = m_shape.createGeometryUserIndex(); @@ -1516,6 +1605,8 @@ void setEditShapeImpl_(EditShape shape, int inputMode, geometry = m_shape.getNextGeometry(geometry); } } + + m_universe_geomID = geomID; m_pointCount = verticesSorter.size(); @@ -1531,8 +1622,7 @@ void setEditShapeImpl_(EditShape shape, int inputMode, m_clusterVertices.setCapacity(m_pointCount); - if ((progress_tracker != null) && !(progress_tracker.progress(-1, -1))) - throw new UserCancelException(); + ProgressTracker.checkAndThrow(progress_tracker); m_clusterData.setCapacity(m_pointCount + 10);// 10 for some self // intersections @@ -1591,35 +1681,40 @@ void setEditShapeImpl_(EditShape shape, int inputMode, ptFirst.setCoords(pt); } } - - if ((progress_tracker != null) && !(progress_tracker.progress(-1, -1))) - throw new UserCancelException(); + + ProgressTracker.checkAndThrow(progress_tracker); m_tmpHalfEdgeParentageIndex = createUserIndexForHalfEdges(); if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { m_tmpHalfEdgeWindingNumberIndex = createUserIndexForHalfEdges(); } + if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + m_tmpHalfEdgeOddEvenNumberIndex = createUserIndexForHalfEdges(); + } + createHalfEdges_(inputMode, verticesSorter);// For each geometry produce // clusters and half edges - // dbg_navigate_(); - + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; + sortHalfEdgesByAngle_(inputMode); + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; - buildChains_(); + buildChains_(inputMode); + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); m_tmpHalfEdgeParentageIndex = -1; - planeSweepParentage_(inputMode, progress_tracker); - // dbg_chk_chain_parents_(); - - if (inputMode != EnumInputMode.enumInputModeBuildGraph) - simplify_(inputMode); + if (m_buildChains) + planeSweepParentage_(inputMode, progress_tracker); + - // dbg_navigate_(); - // dbg_dump_chains_(); + simplify_(inputMode); } void deleteEdgeImpl_(int half_edge) { @@ -1704,32 +1799,26 @@ EditShape getShape() { // calling this! void setEditShape(EditShape shape, ProgressTracker progress_tracker) { setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, - progress_tracker); + progress_tracker, true); } - // Sets an edit shape and simplifies a geometry in it using "odd-even" rule. - // This is the default simplify method for polygons. - // The result edit shape contains a simple polygon. - // The input shape could be a non-simple polygon or a polyline (polyline - // need to form some rings to produce non-empty result). - // The geometry has to be cracked and clustered before calling this! - void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry) { + void setEditShape(EditShape shape, ProgressTracker progress_tracker, boolean bBuildChains) { + setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, + progress_tracker, bBuildChains); + } + + void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry, ProgressTracker progressTracker) { AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); geoms.add(geometry); setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyAlternate, - geoms, null); + geoms, progressTracker, shape.getGeometryType(geometry) == Geometry.Type.Polygon.value()); } - // Sets an edit shape and simplifies a geometry in it using "winding" rule. - // The result edit shape contains a simple polygon. - // The input shape could be a non-simple polygon or a polyline (polyline - // need to form some rings to produce non-empty result). - // The geometry has to be cracked and clustered before calling this! - void setAndSimplifyEditShapeWinding(EditShape shape, int geometry) { + void setAndSimplifyEditShapeWinding(EditShape shape, int geometry, ProgressTracker progressTracker) { AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); geoms.add(geometry); setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyWinding, - geoms, null); + geoms, progressTracker, true); } // Removes shape from the topograph and removes any user index created on @@ -1762,6 +1851,11 @@ void removeShape() { m_tmpHalfEdgeWindingNumberIndex = -1; } + if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeOddEvenNumberIndex); + m_tmpHalfEdgeOddEvenNumberIndex = -1; + } + m_shape = null; m_clusterData.deleteAll(true); m_clusterVertices.deleteAll(true); diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index 96fae909..4e2b02b7 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -33,6 +33,7 @@ final class TopologicalOperations { Point2D m_dummy_pt_2 = new Point2D(); int m_from_edge_for_polylines; boolean m_mask_lookup[] = null; + boolean m_bOGCOutput = false; boolean isGoodParentage(int parentage) { return parentage < m_mask_lookup.length ? m_mask_lookup[parentage] @@ -51,7 +52,7 @@ void cut(int sideIndex, int cuttee, int cutter, return; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } static final class CompareCuts extends IntComparator { @@ -79,10 +80,10 @@ public TopologicalOperations() { m_from_edge_for_polylines = -1; } - void setEditShape(EditShape shape) { + void setEditShape(EditShape shape, ProgressTracker progressTracker) { if (m_topo_graph == null) m_topo_graph = new TopoGraph(); - m_topo_graph.setEditShape(shape, null); + m_topo_graph.setEditShape(shape, progressTracker); } void setEditShapeCrackAndCluster(EditShape shape, double tolerance, @@ -92,9 +93,9 @@ void setEditShapeCrackAndCluster(EditShape shape, double tolerance, .getNextGeometry(geometry)) { if (shape.getGeometryType(geometry) == Geometry.Type.Polygon .value()) - Simplificator.execute(shape, geometry, -1); + Simplificator.execute(shape, geometry, -1, m_bOGCOutput); } - setEditShape(shape); + setEditShape(shape, progressTracker); } private void collectPolygonPathsPreservingFrom_(int geometryFrom, @@ -293,7 +294,7 @@ private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometry, - MultiVertexGeometryImpl.GeometryXSimple.Weak); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); return newGeometry; } @@ -512,7 +513,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); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); int[] result = new int[3];// always returns size 3 result. result[0] = newGeometryMultipoint; @@ -616,6 +617,7 @@ int tryMoveThroughCrossroadBackwards_(int half_edge) { if (isGoodParentage(parentage)) { if (goodEdge != -1) return -1; + goodEdge = e; } @@ -671,19 +673,25 @@ private void restorePolylineParts_(int first_edge, int newGeometry, m_from_edge_for_polylines = -1; int fromEdge = half_edge; int toEdge = -1; + boolean b_found_impassable_crossroad = false; + int edgeCount = 1; while (true) { int halfEdgePrev = m_topo_graph.getHalfEdgePrev(half_edge); if (halfEdgePrev == halfEdgeTwin) break;// the end of a polyline + int halfEdgeTwinNext = m_topo_graph.getHalfEdgeNext(halfEdgeTwin); if (m_topo_graph.getHalfEdgeTwin(halfEdgePrev) != halfEdgeTwinNext) { // Crossroads is here. We can move through the crossroad only if // there is only a single way to pass through. + //When doing planar_simplify we'll never go through the crossroad. half_edge = tryMoveThroughCrossroadBackwards_(half_edge); if (half_edge == -1) break; - else + else { + b_found_impassable_crossroad = true; halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + } } else { half_edge = halfEdgePrev; halfEdgeTwin = halfEdgeTwinNext; @@ -704,6 +712,7 @@ private void restorePolylineParts_(int first_edge, int newGeometry, m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); fromEdge = half_edge; prevailingLength += prevailingDirection_(shape, half_edge); + edgeCount++; } if (toEdge == -1) { @@ -714,14 +723,17 @@ private void restorePolylineParts_(int first_edge, int newGeometry, int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); if (halfEdgeNext == halfEdgeTwin) break; + int halfEdgeTwinPrev = m_topo_graph .getHalfEdgePrev(halfEdgeTwin); if (m_topo_graph.getHalfEdgeTwin(halfEdgeNext) != halfEdgeTwinPrev) { // Crossroads is here. We can move through the crossroad // only if there is only a single way to pass through. half_edge = tryMoveThroughCrossroadForward_(half_edge); - if (half_edge == -1) + if (half_edge == -1) { + b_found_impassable_crossroad = true; break; + } else halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); } else { @@ -738,14 +750,13 @@ private void restorePolylineParts_(int first_edge, int newGeometry, .setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); toEdge = half_edge; prevailingLength += prevailingDirection_(shape, half_edge); + edgeCount++; } } else { // toEdge has been found in the first while loop. This happens when // we go around a face. // Closed loops need special processing as we do not know where the // polyline started or ended. - // TODO: correctly process closed polylines (is_closed_path == - // true). if (m_from_edge_for_polylines != -1) { fromEdge = m_from_edge_for_polylines; @@ -761,7 +772,7 @@ private void restorePolylineParts_(int first_edge, int newGeometry, // Crossroads is here. Pass through the crossroad. toEdge = tryMoveThroughCrossroadBackwards_(fromEdge); if (toEdge == -1) - throw new GeometryException("internal error");// what? + throw GeometryException.GeometryInternalError();// what? } assert (isGoodParentage(getCombinedHalfEdgeParentage_(m_from_edge_for_polylines))); @@ -782,12 +793,22 @@ private void restorePolylineParts_(int first_edge, int newGeometry, fromEdge = m_topo_graph.getHalfEdgeTwin(fromEdge); assert (isGoodParentage(getCombinedHalfEdgeParentage_(fromEdge))); } + int newPath = shape.insertPath(newGeometry, -1);// add new path at the // end half_edge = fromEdge; int cluster = m_topo_graph.getHalfEdgeOrigin(fromEdge); + int clusterLast = m_topo_graph.getHalfEdgeTo(toEdge); + boolean b_closed = clusterLast == cluster; + // The linestrings can touch at boundary points only, while closed path + // has no boundary, therefore no other path can touch it. + // Therefore, if a closed path touches another path, we need to split + // the closed path in two to make the result OGC simple. + boolean b_closed_linestring_touches_other_linestring = b_closed + && b_found_impassable_crossroad; + int vert = selectVertex_(cluster, shape); - assert (vert != -1); + assert(vert != -1); int vertex_dominant = getVertexByID_(vert, geometry_dominant); shape.addVertex(newPath, vertex_dominant); @@ -795,25 +816,36 @@ private void restorePolylineParts_(int first_edge, int newGeometry, m_topo_graph.setClusterUserIndex(cluster, visitedClusters, 1); } + int counter = 0; + int splitAt = b_closed_linestring_touches_other_linestring ? (edgeCount + 1) / 2 : -1; while (true) { int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); int vert_1 = selectVertex_(clusterTo, shape); vertex_dominant = getVertexByID_(vert_1, geometry_dominant); shape.addVertex(newPath, vertex_dominant); + counter++; if (visitedClusters != -1) { m_topo_graph.setClusterUserIndex(clusterTo, visitedClusters, 1); } + if (b_closed_linestring_touches_other_linestring + && counter == splitAt) { + newPath = shape.insertPath(newGeometry, -1);// add new path at + // the end + shape.addVertex(newPath, vertex_dominant); + } + assert (isGoodParentage(getCombinedHalfEdgeParentage_(half_edge))); if (half_edge == toEdge) break; + int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); if (m_topo_graph.getHalfEdgePrev(m_topo_graph .getHalfEdgeTwin(half_edge)) != m_topo_graph .getHalfEdgeTwin(halfEdgeNext)) {// crossroads. half_edge = tryMoveThroughCrossroadForward_(half_edge); if (half_edge == -1) - throw new GeometryException("internal error");// a bug. This + throw GeometryException.GeometryInternalError();// a bug. This // shoulf // never // happen @@ -966,7 +998,7 @@ static MultiPoint processMultiPointIntersectOrDiff_(MultiPoint multi_point, boolean bFirstOut = true; boolean bArea = (intersector.getDimension() == 2); if (intersector.getDimension() != 1 && intersector.getDimension() != 2) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); for (int ipoints = 0; ipoints < npoints;) { int num = multi_point.queryCoordinates(input_points, 1000, ipoints, @@ -1034,7 +1066,7 @@ static Point processPointIntersectOrDiff_(Point point, PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1]; boolean bArea = intersector.getDimension() == 2; if (intersector.getDimension() != 1 && intersector.getDimension() != 2) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); input_points[0] = point.getXY(); if (bArea) PolygonUtils.testPointsInArea2D(intersector, input_points, 1, @@ -1064,8 +1096,11 @@ static Point difference(Point point, Geometry geom, double tolerance) { static Point intersection(Point point, Point point2, double tolerance) { if (point.isEmpty() || point2.isEmpty()) return (Point) point.createInstance(); - if (Point2D.distance(point.getXY(), point2.getXY()) < tolerance) { - return point; + + if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, + point2)) { + return CrackAndCluster.cluster_non_empty_points(point, point2, 1, + 1, 1, 1); } return (Point) point.createInstance(); @@ -1076,7 +1111,9 @@ static Point difference(Point point, Point point2, double tolerance) { return (Point) point.createInstance(); if (point2.isEmpty()) return point; - if (Point2D.distance(point.getXY(), point2.getXY()) < tolerance) { + + if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, + point2)) { return (Point) point.createInstance(); } @@ -1101,77 +1138,83 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, // This method will produce a polygon from a polyline when // b_use_winding_rule_for_polygons is true. This is used by buffer. m_topo_graph = new TopoGraph(); - if (dirty_result - && shape.getGeometryType(geom) != Geometry.Type.MultiPoint - .value()) { - PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); - plane_sweeper.sweepVertical(shape, tolerance); - if (plane_sweeper.hadCompications())// shame. The one pass - // planesweep had some - // complications. Need to do - // full crack and cluster. - { + try + { + if (dirty_result + && shape.getGeometryType(geom) != Geometry.Type.MultiPoint + .value()) { + PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); + plane_sweeper.sweepVertical(shape, tolerance); + if (plane_sweeper.hadCompications())// shame. The one pass + // planesweep had some + // complications. Need to do + // full crack and cluster. + { + CrackAndCluster.execute(shape, tolerance, progress_tracker); + } + } else { CrackAndCluster.execute(shape, tolerance, progress_tracker); } - } else { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + if (!b_use_winding_rule_for_polygons + || shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + + 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) == Geometry.Type.Polygon.value() + || (b_use_winding_rule_for_polygons && shape + .getGeometryType(geom) != Geometry.Type.MultiPoint + .value())) { + // geom can be a polygon or a polyline. + // It can be a polyline only when the winding rule is true. + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + + Polygon polygon = (Polygon) shape.getGeometry(resGeom); + if (!dirty_result) { + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); + } else + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Weak, 0.0, false);// dirty result means + // simple but with 0 + // tolerance. + + return polygon; + } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline + .value()) { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + + Polyline polyline = (Polyline) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return polyline; + } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) { + int resGeom = topoOperationMultiPoint_(); + + MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return mp; + } else { + throw GeometryException.GeometryInternalError(); + } } - if (!b_use_winding_rule_for_polygons - || shape.getGeometryType(geom) == Geometry.Type.MultiPoint - .value()) - m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom); - else - m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom); - - 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) == Geometry.Type.Polygon.value() - || (b_use_winding_rule_for_polygons && shape - .getGeometryType(geom) != Geometry.Type.MultiPoint - .value())) { - // geom can be a polygon or a polyline. - // It can be a polyline only when the winding rule is true. - int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); - - Polygon polygon = (Polygon) shape.getGeometry(resGeom); - if (!dirty_result) { - ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); - } else - ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( - GeometryXSimple.Weak, 0.0, false);// dirty result means - // simple but with 0 - // tolerance. - - return polygon; - } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline - .value()) { - int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); - - Polyline polyline = (Polyline) shape.getGeometry(resGeom); - if (!dirty_result) - ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - - return polyline; - } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint - .value()) { - int resGeom = topoOperationMultiPoint_(); - - MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); - if (!dirty_result) - ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - - return mp; - } else { - throw new GeometryException("internal error"); + finally { + m_topo_graph.removeShape(); } } @@ -1184,6 +1227,13 @@ static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, use_winding_rule_for_polygons, dirty_result, progress_tracker); } + static MultiVertexGeometry simplifyOGC(MultiVertexGeometry input_geom, double tolerance, boolean dirty_result, ProgressTracker progress_tracker) + { + TopologicalOperations topoOps = new TopologicalOperations(); + topoOps.m_bOGCOutput = true; + return topoOps.planarSimplifyImpl_(input_geom, tolerance, false, dirty_result, progress_tracker); + } + public int difference(int geometry_a, int geometry_b) { int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); @@ -1207,7 +1257,7 @@ public int difference(int geometry_a, int geometry_b) { if (dim_a == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } int dissolve(int geometry_a, int geometry_b) { @@ -1239,7 +1289,7 @@ int dissolve(int geometry_a, int geometry_b) { if (dim_a == 0 && dim_b == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } public int intersection(int geometry_a, int geometry_b) { @@ -1275,7 +1325,7 @@ public int intersection(int geometry_a, int geometry_b) { // else return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } int[] intersectionEx(int geometry_a, int geometry_b) { @@ -1315,7 +1365,7 @@ int[] intersectionEx(int geometry_a, int geometry_b) { return res; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } public int symmetricDifference(int geometry_a, int geometry_b) { @@ -1338,7 +1388,7 @@ public int symmetricDifference(int geometry_a, int geometry_b) { if (dim_a == 0 && dim_b == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } int extractShape(int geometry_in) { @@ -1368,7 +1418,7 @@ int extractShape(int geometry_in) { if (dim_a == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } static Geometry normalizeInputGeometry_(Geometry geom) { @@ -1550,7 +1600,7 @@ public static Geometry dissolve(Geometry geometry_a, Geometry geometry_b, } // break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -1635,12 +1685,26 @@ static Geometry dissolveDirty(ArrayList geometries, public static Geometry intersection(Geometry geometry_a, Geometry geometry_b, SpatialReference sr, ProgressTracker progress_tracker) { + Envelope2D env2D_1 = new Envelope2D(); geometry_a.queryEnvelope2D(env2D_1); Envelope2D env2D_2 = new Envelope2D(); geometry_b.queryEnvelope2D(env2D_2); - if (!env2D_1.isIntersecting(env2D_2))// also includes the empty geometry - // cases + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + Envelope2D e = new Envelope2D(); + e.setCoords(env2D_2); + double tol_cluster = InternalUtils + .adjust_tolerance_for_TE_clustering(tolerance); + e.inflate(tol_cluster, tol_cluster); + + if (!env2D_1.isIntersecting(e))// also includes the empty geometry + // cases { if (geometry_a.getDimension() <= geometry_b.getDimension()) return normalizeResult_( @@ -1652,11 +1716,6 @@ public static Geometry intersection(Geometry geometry_a, normalizeInputGeometry_(geometry_b.createInstance()), geometry_a, geometry_b, '&'); } - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify TopologicalOperations topoOps = new TopologicalOperations(); EditShape edit_shape = new EditShape(); @@ -1684,12 +1743,26 @@ public static Geometry intersection(Geometry geometry_a, static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, SpatialReference sr, ProgressTracker progress_tracker) { Geometry[] res_vec = new Geometry[3]; + Envelope2D env2D_1 = new Envelope2D(); geometry_a.queryEnvelope2D(env2D_1); Envelope2D env2D_2 = new Envelope2D(); geometry_b.queryEnvelope2D(env2D_2); - if (!env2D_1.isIntersecting(env2D_2))// also includes the empty geometry - // cases + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + Envelope2D e = new Envelope2D(); + e.setCoords(env2D_2); + double tol_cluster = InternalUtils + .adjust_tolerance_for_TE_clustering(tolerance); + e.inflate(tol_cluster, tol_cluster); + + if (!env2D_1.isIntersecting(e))// also includes the empty geometry + // cases { if (geometry_a.getDimension() <= geometry_b.getDimension()) { Geometry geom = normalizeResult_( @@ -1708,11 +1781,6 @@ static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, } } - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify TopologicalOperations topoOps = new TopologicalOperations(); EditShape edit_shape = new EditShape(); @@ -1829,53 +1897,6 @@ private void flushVertices_(int geometry, AttributeStreamOfInt32 vertices) { shape.setClosedPath(path, true);// need to close polygon rings } - private void removeSpikes_(int cuttee, int cutter) { - int idCuttee = m_topo_graph.getGeometryID(cuttee); - int idCutter = m_topo_graph.getGeometryID(cutter); - int visitedIndex = m_topo_graph.createUserIndexForHalfEdges(); - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - - do { - int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, - visitedIndex); - if (visited != 1) { - int halfEdgeParentage = m_topo_graph - .getHalfEdgeParentage(half_edge); - int halfEdgeFaceParentage = m_topo_graph - .getHalfEdgeFaceParentage(half_edge); - if (halfEdgeParentage != (idCuttee | idCutter) - && halfEdgeFaceParentage != 0) { - int faceHalfEdge = half_edge; - do { - int faceHalfEdgeNext = m_topo_graph - .getHalfEdgeNext(faceHalfEdge); - if (m_topo_graph.getHalfEdgePrev(faceHalfEdge) == m_topo_graph - .getHalfEdgeTwin(faceHalfEdge)) - m_topo_graph.deleteEdgeInternal_(faceHalfEdge); - else - m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, - visitedIndex, 1); - - faceHalfEdge = faceHalfEdgeNext; - } while (faceHalfEdge != half_edge); - } else { - m_topo_graph.setHalfEdgeUserIndex(half_edge, - visitedIndex, 1); - } - } - - half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (half_edge != firstHalfEdge); - } - } - private void setHalfEdgeOrientations_(int orientationIndex, int cutter) { EditShape shape = m_topo_graph.getShape(); @@ -2031,7 +2052,7 @@ private void processPolygonCuts_(int orientationIndex, int sideIndex, private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, AttributeStreamOfInt32 cutHandles) { - removeSpikes_(cuttee, cutter); + m_topo_graph.removeSpikes_(); int orientationIndex = -1; if (sideIndex != -1) { @@ -2056,5 +2077,14 @@ private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, CompareCuts compareCuts = new CompareCuts(shape); cutHandles.Sort(0, cutCount, compareCuts); } + + //call this if EditShape instance has to survive the TopologicalOperations life. + void removeShape() { + if (m_topo_graph != null) { + m_topo_graph.removeShape(); + m_topo_graph = null; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java index e97840a6..d654fa3d 100644 --- a/src/main/java/com/esri/core/geometry/Treap.java +++ b/src/main/java/com/esri/core/geometry/Treap.java @@ -199,19 +199,21 @@ public int addBiggestElement(int element, int treap) { // get_duplicate_element reutrns the node of the already existing element. public int addElementAtPosition(int prevNode, int nextNode, int element, boolean bUnique, boolean bCallCompare, int treap) { - int treap_; - if (treap == -1) { + int treap_ = treap; + if (treap_ == -1) { if (m_defaultTreap == nullNode()) m_defaultTreap = createTreap(-1); treap_ = m_defaultTreap; - } else { - treap_ = treap; } // dbg_check_(m_root); - if (getRoot_(treap_) == nullNode() - || (prevNode == nullNode() && nextNode == nullNode())) - throw new GeometryException("invald call"); + if (getRoot_(treap_) == nullNode()) { + assert (nextNode == nullNode() && prevNode == nullNode()); + int root = newNode_(element); + setRoot_(root, treap_); + addToList_(-1, root, treap_); + return root; + } int cmpNext; int cmpPrev; diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index 02fbc058..7e5afcb6 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -264,9 +264,12 @@ private void geometry_() { m_current_token_type = WktToken.geometrycollection; m_function_stack.add(State.geometryCollectionStart); } else { - String snippet = (m_wkt_string.length() > 200 ? - m_wkt_string.substring(0,200)+"..." : m_wkt_string); - throw new IllegalArgumentException("Could not parse Well-Known Text: "+snippet); + //String snippet = (m_wkt_string.length() > 200 ? m_wkt_string + // .substring(0, 200) + "..." : m_wkt_string); + //throw new IllegalArgumentException( + // "Could not parse Well-Known Text: " + snippet); + throw new IllegalArgumentException( + "Could not parse Well-Known Text around position: " + m_end_token); } m_function_stack.add(State.attributes); 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 a111cf23..4d91d7e2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -195,20 +195,28 @@ public boolean isSimple() { } /** - * isSimpleRelaxed is not supported for the GeometryCollection instance. + * makeSimpleRelaxed is not supported for the GeometryCollection instance. * */ @Override - public boolean isSimpleRelaxed() { + public OGCGeometry makeSimple() { throw new UnsupportedOperationException(); } + @Override + public boolean isSimpleRelaxed() { + for (int i = 0, n = numGeometries(); i < n; i++) + if (!geometryN(i).isSimpleRelaxed()) + return false; + return true; + } + /** - * MakeSimpleRelaxed is not supported for the GeometryCollection instance. + * makeSimpleRelaxed is not supported for the GeometryCollection instance. * */ @Override - public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { throw new UnsupportedOperationException(); } @@ -310,5 +318,9 @@ public OGCGeometry convertToMulti() { return this; } - + + @Override + public String asJson() { + throw new UnsupportedOperationException(); + } } 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 1a6e8b09..9783bb94 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -84,24 +84,27 @@ public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); } - public String toString() { - String snippet = asText(); - if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } - return String.format("%s: %s", this.getClass().getSimpleName(), snippet); - } - public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToWkb); return op.execute(0, getEsriGeometry(), null); } - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(getEsriGeometry()); - } + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(getEsriGeometry()); + } + + /** + * + * @return Convert to REST JSON. + */ + public String asJson() { + return GeometryEngine.geometryToJson(esriSR, getEsriGeometry()); + } + public boolean isEmpty() { return getEsriGeometry().isEmpty(); } @@ -137,10 +140,12 @@ public double MaxMeasure() { * "simple" for each geometry type. * * The method has O(n log n) complexity when the input geometry is simple. - * For non-simple geometries, it terminates immediately when the first issue is - * encountered. + * For non-simple geometries, it terminates immediately when the first issue + * is encountered. * * @return True if geometry is simple and false otherwise. + * + * Note: If isSimple is true, then isSimpleRelaxed is true too. */ public boolean isSimple() { return OperatorSimplifyOGC.local().isSimpleOGC(getEsriGeometry(), @@ -151,26 +156,47 @@ public boolean isSimple() { * Extension method - checks if geometry is simple for Geodatabase. * * @return Returns true if geometry is simple, false otherwise. + * + * Note: If isSimpleRelaxed is true, then isSimple is either true or false. Geodatabase has more relaxed requirements for simple geometries than OGC. */ public boolean isSimpleRelaxed() { OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Simplify); - return op - .isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); + return op.isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); } + @Deprecated + /** + * Use makeSimpleRelaxed instead. + */ + public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + return makeSimpleRelaxed(forceProcessing); + } /** * Makes a simple geometry for Geodatabase. * * @return Returns simplified geometry. + * + * Note: isSimpleRelaxed should return true after this operation. */ - public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Simplify); return OGCGeometry.createFromEsriGeometry( op.execute(getEsriGeometry(), esriSR, forceProcessing, null), esriSR); } + + /** + * Resolves topological issues in this geometry and makes it Simple according to OGC specification. + * + * @return Returns simplified geometry. + * + * Note: isSimple and isSimpleRelaxed should return true after this operation. + */ + public OGCGeometry makeSimple() { + return simplifyBunch_(getEsriGeometryCursor()); + } public boolean is3D() { return getEsriGeometry().getDescription().hasAttribute( @@ -184,14 +210,17 @@ public boolean isMeasured() { abstract public OGCGeometry boundary(); - // query + /** + * OGC equals + * + */ public boolean equals(OGCGeometry another) { 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()); } - + public boolean disjoint(OGCGeometry another) { com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); @@ -264,14 +293,6 @@ public double distance(OGCGeometry another) { // As a result there are at most three geometries, each geometry is Simple. // Afterwards // it produces a single OGCGeometry. - // - // Note: Not complete yet. We'll use this method to implement the OGC - // Simplify (or MakeValid method) - // At this moment, method removes self intersections, and clusters vertices, - // but may sometimes - // produce geometries with self-tangency or polygons with disconnected - // interior - // which are simple for ArcObjects, but non-simple for OGC. private OGCGeometry simplifyBunch_(GeometryCursor gc) { // Combines geometries into multipoint, polyline, and polygon types, // simplifying them and unioning them, @@ -640,10 +661,22 @@ protected boolean isConcreteGeometryCollection() { public void setSpatialReference(SpatialReference esriSR_) { esriSR = esriSR_; } - + /** - *Converts this Geometry to the OGCMulti* if it is not OGCMulti* or OGCGeometryCollection already. + * Converts this Geometry to the OGCMulti* if it is not OGCMulti* or + * OGCGeometryCollection already. + * * @return OGCMulti* or OGCGeometryCollection instance. */ public abstract OGCGeometry convertToMulti(); -} + + @Override + public String toString() { + String snippet = asText(); + if (snippet.length() > 200) { + snippet = snippet.substring(0, 197) + "..."; + } + return String + .format("%s: %s", this.getClass().getSimpleName(), snippet); + } +} \ No newline at end of file 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 c9f9fcff..5a038d2a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -49,7 +49,7 @@ public ByteBuffer asBinary() { * Returns the exterior ring of this Polygon. * @return OGCLinearRing instance. */ - public OGCLineString exterorRing() { + public OGCLineString exteriorRing() { if (polygon.isEmpty()) return new OGCLinearRing((Polygon) polygon.createInstance(), 0, esriSR, true); diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index de3bcf04..c1af6866 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -41,34 +41,6 @@ static Geometry getGeometryFromJSon(String jsonStr) { } } - public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { - return; - /*int count1 = mp1.getPointCount(); - int count2 = mp2.getPointCount(); - - System.out.println("From Rest vertices count: " + count1); - System.out.println("From Borg count: " + count2); - // Assert.assertTrue(count1==count2); - - int len = mp1.getPointCount(); - - for (int i = 0; i < len; i++) { - Point p = mp1.getPoint(i); - Point p2 = mp2.getPoint(i); - System.out.println("for rest: [" + p.getX() + "," + p.getY() + "]"); - System.out.println("for proj: [" + p2.getX() + "," + p2.getY() - + "]"); - @SuppressWarnings("unused") - double deltaX = p2.getX() - p.getX(); - @SuppressWarnings("unused") - double deltaY = p2.getY() - p.getY(); - - // Assert.assertTrue(deltaX<1e-7); - // Assert.assertTrue(deltaY<1e-7); - } - */ - } - public enum SpatialRelationType { esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation } diff --git a/src/test/java/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java index fe3f6a67..bd59cc9b 100644 --- a/src/test/java/com/esri/core/geometry/TestAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestAttributes.java @@ -1,10 +1,11 @@ package com.esri.core.geometry; import static org.junit.Assert.*; +import junit.framework.TestCase; import org.junit.Test; -public class TestAttributes { +public class TestAttributes extends TestCase{ @Test public void testPoint() { diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java index 4ffd03cf..b7bc7d2e 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -49,7 +49,7 @@ public static void testEnvelope2Dintersector() { intersector.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector.addEnvelope(envelopes.get(i)); + intersector.addEnvelope(i, envelopes.get(i)); intersector.endConstruction(); int count = 0; @@ -69,7 +69,7 @@ public static void testEnvelope2Dintersector() { intersector2.setTolerance(0.0); intersector2.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector2.addEnvelope(envelopes.get(i)); + intersector2.addEnvelope(i, envelopes.get(i)); intersector2.endConstruction(); count = 0; @@ -100,7 +100,7 @@ public static void testEnvelope2Dintersector() { intersector3.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector3.addEnvelope(envelopes.get(i)); + intersector3.addEnvelope(i, envelopes.get(i)); intersector3.endConstruction(); ; count = 0; @@ -128,7 +128,7 @@ public static void testEnvelope2Dintersector() { intersector4.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector4.addEnvelope(envelopes.get(i)); + intersector4.addEnvelope(i, envelopes.get(i)); intersector4.endConstruction(); count = 0; @@ -156,7 +156,7 @@ public static void testEnvelope2Dintersector() { intersector5.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector5.addEnvelope(envelopes.get(i)); + intersector5.addEnvelope(i, envelopes.get(i)); intersector5.endConstruction(); count = 0; @@ -305,12 +305,12 @@ public static void testRandom() { intersector.startRedConstruction(); for (int i = 0; i < envelopes_red.size(); i++) - intersector.addRedEnvelope(envelopes_red.get(i)); + intersector.addRedEnvelope(i, envelopes_red.get(i)); intersector.endRedConstruction(); intersector.startBlueConstruction(); for (int i = 0; i < envelopes_blue.size(); i++) - intersector.addBlueEnvelope(envelopes_blue.get(i)); + intersector.addBlueEnvelope(i, envelopes_blue.get(i)); intersector.endBlueConstruction(); while (intersector.next()) diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index aac65c3c..9c7a915d 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -36,7 +36,6 @@ public void testGeometryOperationSupport() { } catch (IllegalArgumentException ex) { noException = 0; } catch (GeometryException ex) { - System.out.println(ex.internalCode); noException = 0; } assertEquals(noException, 1); diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 0c788cc2..d377e6f6 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -69,4 +69,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 9a12ac11..2de57b43 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -552,7 +552,6 @@ boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, return false; if (!(Wkt != null && Wkt.length() > 0)) return false; - //System.out.println("WKT1: " + Wkt); SpatialReference sr2 = SpatialReference.create(Wkt); int wki2 = sr2.getID(); if (expectWki2 > 0) { diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 9b4f19a3..99adee7a 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -1014,7 +1014,7 @@ public static void testImportExportWktMultiPolygon() { // 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, 80 80 7, 70 80 7, 70 70 7)))")); + .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) { } @@ -1032,7 +1032,7 @@ public static void testImportExportWktMultiPolygon() { 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, 90 90 7, 60 90 7, 60 60 7), (70 70 7, 80 80 7, 70 80 7, 70 70 7))")); + .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) { } diff --git a/src/test/java/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java index f5276d69..8bdcd3c8 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersect2.java +++ b/src/test/java/com/esri/core/geometry/TestIntersect2.java @@ -358,7 +358,6 @@ public void testMultiPointAndMultiPoint3() { SpatialReference.create(4326)); } catch (Exception ex) { noException = 0; - System.out.println("err: " + ex.getMessage()); } assertEquals(noException, 1); assertTrue(intersectGeom.isEmpty()); diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index 66e0bc49..ab7dd9f0 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -1002,4 +1002,40 @@ public void testIntersectionIssueLinePoly1() { assertTrue(((Polyline)res).getPathCount() == 1); assertTrue(((Polyline)res).getPointCount() == 4); } + + @Test + public void testSharedEdgeIntersection_13() + { + String s1 = "{\"rings\":[[[0.099604024000029767,0.2107958250000479],[0.14626826900007472,0.2107958250000479],[0.14626826900007472,0.18285316400005058],[0.099604024000029767,0.18285316400005058],[0.099604024000029767,0.2107958250000479]]]}"; + String s2 = "{\"paths\":[[[0.095692051000071388,0.15910190100004229],[0.10324853600002371,0.18285316400004228],[0.12359292700006108,0.18285316400004228],[0.12782611200003657,0.1705583920000322],[0.13537063000007138,0.18285316400004228]]]}"; + + Polygon polygon = (Polygon)TestCommonMethods.fromJson(s1).getGeometry(); + Polyline polyline = (Polyline)TestCommonMethods.fromJson(s2).getGeometry(); + SpatialReference sr = SpatialReference.create(4326); + + Geometry g = OperatorIntersection.local().execute(polygon, polyline, sr, null); + assertTrue(!g.isEmpty()); + } + + @Test + public void testIntersectionIssue2() { + String s1 = "{\"rings\":[[[-97.174860352323378,48.717174479818425],[-97.020624513410553,58.210155436624177],[-94.087641114245969,58.210155436624177],[-94.087641114245969,48.639781902013226],[-97.174860352323378,48.717174479818425]]]}"; + String s2 = "{\"rings\":[[[-94.08764111399995,52.68342763000004],[-94.08764111399995,56.835188018000053],[-90.285921520999977,62.345706350000057],[-94.08764111399995,52.68342763000004]]]}"; + + Polygon polygon1 = (Polygon)TestCommonMethods.fromJson(s1).getGeometry(); + Polygon polygon2 = (Polygon)TestCommonMethods.fromJson(s2).getGeometry(); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor res = OperatorIntersection.local().execute(new SimpleGeometryCursor(polygon1), new SimpleGeometryCursor(polygon2), sr, null, 2); + Geometry g = res.next(); + assertTrue(g != null); + assertTrue(!g.isEmpty()); + Geometry g2 = res.next(); + assertTrue(g2 == null); + + String ss = "{\"paths\":[[[-94.08764111412296,52.68342763000004],[-94.08764111410767,56.83518801800005]]]}"; + Polyline polyline = (Polyline)TestCommonMethods.fromJson(ss).getGeometry(); + boolean eq = OperatorEquals.local().execute(g, polyline, sr, null); + assertTrue(eq); + } } diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index de188f3e..a2214e5e 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -464,13 +464,11 @@ public void testSpatialRef() throws JsonParseException, IOException { mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); // showProjectedGeometryInfo(mapGeom); - // System.out.println("\n\nWKID: "+ - // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); try { GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); } catch (Exception ex) { - System.out.print(ex.getMessage()); + Assert.assertTrue("Should not throw for invalid wkid", false); } } @@ -629,13 +627,13 @@ public void testGeometryToJSON() { String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test // WKID // == -1 - System.out.println("Geom JSON STRING is" + outputPolygon1); + //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); + //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); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 816813bd..dcca0c3a 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -6,6 +6,7 @@ import com.esri.core.geometry.ogc.OGCGeometryCollection; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCMultiCurve; +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; @@ -14,6 +15,7 @@ import org.codehaus.jackson.JsonParseException; import org.json.JSONException; +import org.junit.Test; import java.io.IOException; import java.nio.ByteBuffer; @@ -29,6 +31,7 @@ protected void tearDown() throws Exception { super.tearDown(); } + @Test public void testPoint() { OGCGeometry g = OGCGeometry.fromText("POINT(1 2)"); assertTrue(g.geometryType().equals("Point")); @@ -44,13 +47,14 @@ public void testPoint() { assertTrue(Math.abs(a - 400) < 1e-1); } + @Test public void testPolygon() { 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")); OGCPolygon p = (OGCPolygon) g; assertTrue(p.numInteriorRing() == 1); - OGCLineString ls = p.exterorRing(); + OGCLineString ls = p.exteriorRing(); // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); boolean b = ls .equals(OGCGeometry @@ -66,6 +70,7 @@ public void testPolygon() { 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 testGeometryCollection() throws JSONException { OGCGeometry g = OGCGeometry .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); @@ -137,13 +142,14 @@ public void testGeometryCollection() throws JSONException { } + @Test public void testFirstPointOfPolygon() { 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")); OGCPolygon p = (OGCPolygon) g; assertTrue(p.numInteriorRing() == 1); - OGCLineString ls = p.exterorRing(); + OGCLineString ls = p.exteriorRing(); OGCPoint p1 = ls.pointN(1); assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); OGCPoint p2 = ls.pointN(3); @@ -155,6 +161,7 @@ public void testFirstPointOfPolygon() { } + @Test public void testFirstPointOfLineString() { OGCGeometry g = OGCGeometry .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)"); @@ -167,6 +174,7 @@ public void testFirstPointOfLineString() { assertTrue(ms.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))")); } + @Test public void testPointInPolygon() { 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))"); @@ -179,6 +187,7 @@ public void testPointInPolygon() { assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); } + @Test public void testMultiPolygon() { { OGCGeometry g = OGCGeometry @@ -212,6 +221,7 @@ public void testMultiPolygon() { } } + @Test public void testMultiPolygonUnion() { 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))"); @@ -228,6 +238,7 @@ public void testMultiPolygonUnion() { assertTrue(u.contains(OGCGeometry.fromText("POINT(100 100)"))); } + @Test public void testIntersection() { OGCGeometry g = OGCGeometry.fromText("LINESTRING(0 0, 10 10)"); OGCGeometry g2 = OGCGeometry.fromText("LINESTRING(10 0, 0 10)"); @@ -237,6 +248,7 @@ public void testIntersection() { assertTrue(u.equals(OGCGeometry.fromText("POINT(5 5)"))); } + @Test public void testPointSymDif() { OGCGeometry g1 = OGCGeometry.fromText("POINT(1 2)"); OGCGeometry g2 = OGCGeometry.fromText("POINT(3 4)"); @@ -249,6 +261,7 @@ public void testPointSymDif() { } + @Test public void testNullSr() { String wkt = "point (0 0)"; OGCGeometry g = OGCGeometry.fromText(wkt); @@ -256,6 +269,7 @@ public void testNullSr() { assertTrue(g.SRID() < 1); } + @Test public void testIsectPoint() { String wkt = "point (0 0)"; String wk2 = "point (0 0)"; @@ -271,6 +285,7 @@ public void testIsectPoint() { } } + @Test public void testIsectDisjoint() { String wk3 = "linestring (0 0, 1 1)"; String wk4 = "linestring (2 2, 4 4)"; @@ -286,6 +301,7 @@ public void testIsectDisjoint() { } } + @Test public void test_polygon_is_simple_for_OGC() { try { { @@ -395,10 +411,20 @@ public void test_polygon_is_simple_for_OGC() { } } - /* - This will fail + @Test public void test_polygon_simplify_for_OGC() { try { + { + //degenerate + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [20, 0], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); + assertTrue(og.isSimple()); + } { String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; OGCGeometry g = OGCGeometry.fromJson(s); @@ -407,8 +433,10 @@ public void test_polygon_simplify_for_OGC() { assertTrue(g.isSimpleRelaxed()); Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); assertTrue(og.geometryType().equals("Polygon")); assertTrue(((OGCPolygon)og).numInteriorRing() == 0); + assertTrue(og.isSimple()); } {// exterior ring is self-tangent @@ -461,6 +489,7 @@ public void test_polygon_simplify_for_OGC() { assertTrue(g.isSimpleRelaxed()); Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); res = og.isSimple(); assertTrue(res); assertTrue(og.geometryType().equals("Polygon")); @@ -551,12 +580,34 @@ public void test_polygon_simplify_for_OGC() { assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(0)).numInteriorRing() == 0); assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(1)).numInteriorRing() == 0); } + + + { + OGCGeometry g = OGCGeometry.fromJson("{\"rings\":[[[-3,4],[6,4],[6,-3],[-3,-3],[-3,4]],[[0,2],[2,2],[0,0],[4,0],[4,2],[2,0],[2,2],[4,2],[3,3],[2,2],[1,3],[0,2]]], \"spatialReference\":{\"wkid\":4326}}"); + assertTrue(g.geometryType().equals("Polygon")); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + OGCGeometry simpleG = g.makeSimple(); + assertTrue(simpleG.geometryType().equals("MultiPolygon")); + assertTrue(simpleG.isSimple()); + OGCMultiPolygon mp = (OGCMultiPolygon)simpleG; + assertTrue(mp.numGeometries() == 2); + OGCPolygon g1 = (OGCPolygon)mp.geometryN(0); + OGCPolygon g2 = (OGCPolygon)mp.geometryN(1); + assertTrue((g1.numInteriorRing() == 0 && g1.numInteriorRing() == 2) || + (g1.numInteriorRing() == 2 && g2.numInteriorRing() == 0)); + + OGCGeometry oldOutput = OGCGeometry.fromJson("{\"rings\":[[[-3,-3],[-3,4],[6,4],[6,-3],[-3,-3]],[[0,0],[2,0],[4,0],[4,2],[3,3],[2,2],[1,3],[0,2],[2,2],[0,0]],[[2,0],[2,2],[4,2],[2,0]]],\"spatialReference\":{\"wkid\":4326}}"); + assertTrue(oldOutput.isSimpleRelaxed()); + assertFalse(oldOutput.isSimple()); + } } catch (Exception ex) { assertTrue(false); } } - */ + @Test public void test_polyline_is_simple_for_OGC() { try { { @@ -658,6 +709,7 @@ public void test_polyline_is_simple_for_OGC() { } + @Test public void test_multipoint_is_simple_for_OGC() { try { @@ -690,6 +742,7 @@ public void test_multipoint_is_simple_for_OGC() { } + @Test public void testGeometryCollectionBuffer() { OGCGeometry g = OGCGeometry .fromText("GEOMETRYCOLLECTION(POINT(1 1), POINT(1 1), POINT(1 2), LINESTRING (0 0, 1 1, 1 0, 0 1), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); @@ -699,6 +752,7 @@ public void testGeometryCollectionBuffer() { assertTrue(simpleG.geometryType().equals("GeometryCollection")); } + @Test public void testIsectTria1() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; @@ -713,6 +767,7 @@ public void testIsectTria1() { String s = rslt.asText(); } + @Test public void testIsectTriaJson1() throws JsonParseException, IOException { 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}}"; @@ -725,6 +780,7 @@ public void testIsectTriaJson1() throws JsonParseException, IOException { String s = GeometryEngine.geometryToJson(rslt.getEsriSpatialReference().getID(), rslt.getEsriGeometry()); } + @Test public void testIsectTria2() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; @@ -739,6 +795,7 @@ public void testIsectTria2() { String s = rslt.asText(); } + @Test public void testIsectTria3() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; @@ -754,6 +811,7 @@ public void testIsectTria3() { String s = rslt.asText(); } + @Test public void testMultiPointSinglePoint() { String wkt = "multipoint((1 0))"; OGCGeometry g0 = OGCGeometry.fromText(wkt); @@ -771,6 +829,7 @@ public void testMultiPointSinglePoint() { } + @Test 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; @@ -786,5 +845,44 @@ public void testWktMultiPolygon() { 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)))")); } + + @Test + public void testMultiPolygonArea() { + //MultiPolygon Area #36 + String wkt = "MULTIPOLYGON (((1001200 2432900, 1001420 2432691, 1001250 2432388, 1001498 2432325, 1001100 2432100, 1001500 2431900, 1002044 2431764, 1002059 2432120, 1002182 2432003, 1002400 2432300, 1002650 2432150, 1002610 2432323, 1002772 2432434, 1002410 2432821, 1002700 2433000, 1001824 2432866, 1001600 2433150, 1001200 2432900)), ((1000393 2433983, 1000914 2434018, 1000933 2433817, 1000568 2433834, 1000580 2433584, 1000700 2433750, 1000800 2433650, 1000700 2433450, 1000600 2433550, 1000200 2433350, 1000100 2433900, 1000393 2433983)), ((1001200 2432900, 1000878 2432891, 1000900 2433300, 1001659 2433509, 1001600 2433150, 1001200 2432900)), ((1002450 2431650, 1002300 2431650, 1002300 2431900, 1002500 2432100, 1002600 2431800, 1002450 2431800, 1002450 2431650)), ((999750 2433550, 999850 2433600, 999900 2433350, 999780 2433433, 999750 2433550)), ((1002950 2432050, 1003005 2431932, 1002850 2432250, 1002928 2432210, 1002950 2432050)), ((1002600 2431750, 1002642 2431882, 1002750 2431900, 1002750 2431750, 1002600 2431750)), ((1002950 2431750, 1003050 2431650, 1002968 2431609, 1002950 2431750)))"; + { + OGCGeometry ogcg = OGCGeometry.fromText(wkt); + assertTrue(ogcg.geometryType().equals("MultiPolygon")); + OGCMultiPolygon mp = (OGCMultiPolygon)ogcg; + double a = mp.area(); + assertTrue(Math.abs(mp.area() - 2037634.5) < a*1e-14); + } + + { + OGCGeometry ogcg = OGCGeometry.fromText(wkt); + assertTrue(ogcg.geometryType().equals("MultiPolygon")); + Geometry g = ogcg.getEsriGeometry(); + double a = g.calculateArea2D(); + assertTrue(Math.abs(a - 2037634.5) < a*1e-14); + } + } + + @Test + public void testPolylineSimplifyIssueGithub52() throws JsonParseException, IOException { + String json = "{\"paths\":[[[2,0],[4,3],[5,1],[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"; + { + OGCGeometry g = OGCGeometry.fromJson(json); + assertTrue(g.geometryType().equals("LineString")); + OGCGeometry simpleG = g.makeSimple();//make ogc simple + assertTrue(simpleG.geometryType().equals("MultiLineString")); + assertTrue(simpleG.isSimpleRelaxed());//geodatabase simple + assertTrue(simpleG.isSimple());//ogc simple + OGCMultiLineString mls =(OGCMultiLineString)simpleG; + assertTrue(mls.numGeometries() == 4); + OGCGeometry baseGeom = OGCGeometry.fromJson("{\"paths\":[[[2,0],[3.25,1.875]],[[3.25,1.875],[4,3],[5,1]],[[5,1],[3.25,1.875]],[[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"); + assertTrue(simpleG.equals(baseGeom)); + + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java index 7ef61a01..1bd18b9a 100644 --- a/src/test/java/com/esri/core/geometry/TestOffset.java +++ b/src/test/java/com/esri/core/geometry/TestOffset.java @@ -143,8 +143,6 @@ public void OffsetPolygon_(double distance, JoinType joins) { Geometry outputGeom = offset.execute(polygon, null, distance, joins, 2, 0, null); - System.out.println(GeometryUtils.getJSonStringFromGeometry(outputGeom, - null)); assertNotNull(outputGeom); if (distance > 2) { diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index 1a0bb15c..df53b170 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -1,7 +1,9 @@ package com.esri.core.geometry; import java.util.Random; + import junit.framework.TestCase; + import org.junit.Test; public class TestPoint extends TestCase { @@ -41,7 +43,6 @@ public void testEnvelope2000() { fullExtent.merge(geomExtent); } long endTime = System.nanoTime(); - System.out.println((endTime - startTime) / 1.0e6); } } @@ -110,5 +111,59 @@ public void testCopy() { copyPt = (Point) pt.copy(); assertTrue(copyPt.equals(pt)); assertTrue(copyPt.getXY().isEqual(new Point2D(11, 13))); + + assertTrue(copyPt.getXY().equals((Object)new Point2D(11, 13))); } + + @Test + public void testEnvelope2D_corners() { + Envelope2D env = new Envelope2D(0, 1, 2, 3); + assertFalse(env.equals(null)); + assertTrue(env.equals((Object)new Envelope2D(0, 1, 2, 3))); + + Point2D pt2D = env.getLowerLeft(); + assertTrue(pt2D.equals(Point2D.construct(0, 1))); + pt2D = env.getUpperLeft(); + assertTrue(pt2D.equals(Point2D.construct(0, 3))); + pt2D = env.getUpperRight(); + assertTrue(pt2D.equals(Point2D.construct(2, 3))); + pt2D = env.getLowerRight(); + assertTrue(pt2D.equals(Point2D.construct(2, 1))); + + { + Point2D[] corners = new Point2D[4]; + env.queryCorners(corners); + assertTrue(corners[0].equals(Point2D.construct(0, 1))); + assertTrue(corners[1].equals(Point2D.construct(0, 3))); + assertTrue(corners[2].equals(Point2D.construct(2, 3))); + assertTrue(corners[3].equals(Point2D.construct(2, 1))); + + env.queryCorners(corners); + assertTrue(corners[0].equals(env.queryCorner(0))); + assertTrue(corners[1].equals(env.queryCorner(1))); + assertTrue(corners[2].equals(env.queryCorner(2))); + assertTrue(corners[3].equals(env.queryCorner(3))); + } + + { + Point2D[] corners = new Point2D[4]; + env.queryCornersReversed(corners); + assertTrue(corners[0].equals(Point2D.construct(0, 1))); + assertTrue(corners[1].equals(Point2D.construct(2, 1))); + assertTrue(corners[2].equals(Point2D.construct(2, 3))); + assertTrue(corners[3].equals(Point2D.construct(0, 3))); + + env.queryCornersReversed(corners); + assertTrue(corners[0].equals(env.queryCorner(0))); + assertTrue(corners[1].equals(env.queryCorner(3))); + assertTrue(corners[2].equals(env.queryCorner(2))); + assertTrue(corners[3].equals(env.queryCorner(1))); + } + + assertTrue(env.getCenter().equals(Point2D.construct(1, 2))); + + assertFalse(env.containsExclusive(env.getUpperLeft())); + assertTrue(env.contains(env.getUpperLeft())); + assertTrue(env.containsExclusive(env.getCenter())); + } } diff --git a/src/test/java/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java index a0766cd5..f06c8ce9 100644 --- a/src/test/java/com/esri/core/geometry/TestProximity2D.java +++ b/src/test/java/com/esri/core/geometry/TestProximity2D.java @@ -130,6 +130,18 @@ public void testProximity_2D_1() { true); resultPoint0 = result0.getCoordinate(); assertTrue(resultPoint0.getX() == 0.0 && resultPoint0.getY() == 2.0); + + Polygon pp = new Polygon(); + pp.startPath(0, 0); + pp.lineTo(0, 10); + pp.lineTo(10, 10); + pp.lineTo(10, 0); + + inputPoint.setXY(15, -5); + + result0 = proximityOp.getNearestCoordinate(pp, inputPoint, true, true); + boolean is_right = result0.isRightSide(); + assertTrue(!is_right); } Polygon MakePolygon() { @@ -227,13 +239,24 @@ public static void testProximity2D_3() { point.setXY(-110, 20); Proximity2DResult result = proximity.getNearestCoordinate(polygon, point, false); - System.out.println("The closest coordinate is " - + result.getCoordinate()); - System.out.println("The closest point is " + result.getDistance()); Point point2 = new Point(); point2.setXY(-120, 12); @SuppressWarnings("unused") Proximity2DResult[] results = proximity.getNearestVertices(polygon, point2, 10, 12); } + + @Test + public static void testCR254240() { + OperatorProximity2D proximityOp = OperatorProximity2D.local(); + + Point inputPoint = new Point(-12, 12); + Polyline line = new Polyline(); + line.startPath(-10, 0); + line.lineTo(0, 0); + + Proximity2DResult result = proximityOp.getNearestCoordinate(line, + inputPoint, false, true); + assertTrue(result.isRightSide() == false); + } } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index e02ed168..e12c1d4a 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -1,10 +1,13 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; import org.junit.Test; +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + public class TestRelation extends TestCase { @Override protected void setUp() throws Exception { @@ -273,7 +276,7 @@ public static void testContainsFailureCR186456() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); 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]]]}"; - MapGeometry mg = importFromJson(str); + MapGeometry mg = TestCommonMethods.fromJson(str); boolean res = op.execute((mg.getGeometry()), (mg.getGeometry()), null, null); assertTrue(res); @@ -286,9 +289,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -303,9 +306,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[100,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[100,10]]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -319,9 +322,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -334,9 +337,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -349,9 +352,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -367,9 +370,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -383,9 +386,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[10,10]]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -399,9 +402,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -414,9 +417,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -429,9 +432,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -458,9 +461,9 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -473,8 +476,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(300, 0); mg2.getGeometry().applyTransformation(trans); @@ -489,8 +492,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(30, 0); mg2.getGeometry().applyTransformation(trans); @@ -505,8 +508,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(0, 0); mg2.getGeometry().applyTransformation(trans); @@ -522,8 +525,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(0, 0); mg2.getGeometry().applyTransformation(trans); @@ -539,8 +542,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(10, 0); mg2.getGeometry().applyTransformation(trans); @@ -556,8 +559,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(200, 0); mg2.getGeometry().applyTransformation(trans); @@ -573,8 +576,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"points\":[[0,0],[0,200],[200,200],[200,0]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(0, 0); mg2.getGeometry().applyTransformation(trans); @@ -589,9 +592,9 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -603,9 +606,9 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200], [0,2]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -629,11 +632,18 @@ public static void testPolygonPolygonEquals() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[0,5],[0,7],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; - Polygon polygon1 = (Polygon) importFromJson(str1).getGeometry(); - Polygon polygon2 = (Polygon) importFromJson(str2).getGeometry(); + Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(str1) + .getGeometry(); + Polygon polygon2 = (Polygon) TestCommonMethods.fromJson(str2) + .getGeometry(); // wiggleGeometry(polygon1, tolerance, 1982); // wiggleGeometry(polygon2, tolerance, 511); + equals.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + equals.accelerateGeometry(polygon2, sr, + Geometry.GeometryAccelerationDegree.enumHot); + boolean res = equals.execute(polygon1, polygon2, sr, null); assertTrue(res); equals.execute(polygon2, polygon1, sr, null); @@ -643,8 +653,8 @@ public static void testPolygonPolygonEquals() { // a hole. str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[0,10],[10,10],[5,10],[10,10],[10,0],[0,0],[0,10]]]}"; - polygon1 = (Polygon) importFromJson(str1).getGeometry(); - polygon2 = (Polygon) importFromJson(str2).getGeometry(); + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -655,8 +665,8 @@ public static void testPolygonPolygonEquals() { str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; - polygon1 = (Polygon) importFromJson(str1).getGeometry(); - polygon2 = (Polygon) importFromJson(str2).getGeometry(); + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -667,8 +677,8 @@ public static void testPolygonPolygonEquals() { str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; str2 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"; - polygon1 = (Polygon) importFromJson(str1).getGeometry(); - polygon2 = (Polygon) importFromJson(str2).getGeometry(); + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -835,8 +845,10 @@ public static void testPolygonPolygonDisjoint() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = disjoint.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -847,8 +859,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -861,8 +873,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -875,8 +887,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = disjoint.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -887,8 +899,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[5,5],[5,0]],[[10,0],[10,10],[20,10],[20,0]]]}"; str2 = "{\"rings\":[[[0,-10],[0,-5],[5,-5],[5,-10]],[[11,1],[11,9],[19,9],[19,1]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = disjoint.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -908,8 +920,10 @@ public static void testPolylinePolylineDisjoint() { String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polyline1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -922,8 +936,8 @@ public static void testPolylinePolylineDisjoint() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polyline1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -936,8 +950,8 @@ public static void testPolylinePolylineDisjoint() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = disjoint.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1044,6 +1058,8 @@ public static void testPolylineMultiPointDisjoint() { polyline1.lineTo(1, 0); polyline1.lineTo(1, 1); + disjoint.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); res = disjoint.execute(polyline1, multipoint2, sr, null); assertTrue(!res); res = disjoint.execute(multipoint2, polyline1, sr, null); @@ -1082,6 +1098,11 @@ public static void testPolylinePointDisjoint() { point2.setXY(4, 2); + polyline1 = (Polyline) OperatorDensifyByLength.local().execute( + polyline1, 0.1, null); + disjoint.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polyline1, point2, sr, null); assertTrue(!res); res = disjoint.execute(point2, polyline1, sr, null); @@ -1328,8 +1349,10 @@ public static void testPolygonPolygonTouches() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1342,8 +1365,8 @@ public static void testPolygonPolygonTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1357,8 +1380,8 @@ public static void testPolygonPolygonTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1371,8 +1394,8 @@ public static void testPolygonPolygonTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -1408,8 +1431,10 @@ public static void testPolygonPolylineTouches() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1422,8 +1447,8 @@ public static void testPolygonPolylineTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1435,8 +1460,8 @@ public static void testPolygonPolylineTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1448,8 +1473,8 @@ public static void testPolygonPolylineTouches() { str1 = "{\"rings\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1471,8 +1496,10 @@ public static void testPolylinePolylineTouches() { String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = touches.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1483,8 +1510,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -1495,8 +1522,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -1508,8 +1535,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -1522,8 +1549,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1533,8 +1560,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1634,6 +1661,8 @@ public static void testPolylineMultiPointTouches() { polyline1.lineTo(1, 0); polyline1.lineTo(1, 1); + touches.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); res = touches.execute(polyline1, multipoint2, sr, null); assertTrue(res); res = touches.execute(multipoint2, polyline1, sr, null); @@ -1696,6 +1725,9 @@ public static void testPolylineMultiPointCrosses() { res = crosses.execute(multipoint2, polyline1, sr, null); assertTrue(!res); + crosses.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + multipoint2.add(1, 0); res = crosses.execute(polyline1, multipoint2, sr, null); assertTrue(res); @@ -1747,8 +1779,10 @@ public static void testPolygonPolygonOverlaps() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1761,8 +1795,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1776,8 +1810,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1790,8 +1824,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = overlaps.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -1801,8 +1835,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = overlaps.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -2032,8 +2066,10 @@ public static void testPolygonPolygonWithin() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[-1,-1],[-1,11],[11,11],[11,-1],[-1,-1]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = within.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -2044,8 +2080,8 @@ public static void testPolygonPolygonWithin() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -2056,8 +2092,8 @@ public static void testPolygonPolygonWithin() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[-1,0],[-1,11],[11,11],[11,0],[-1,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -2068,8 +2104,8 @@ public static void testPolygonPolygonWithin() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = within.execute(polygon2, polygon1, sr, null); assertTrue(!res); @@ -2289,8 +2325,10 @@ public static void testPolylinePolylineCrosses() { String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -2301,8 +2339,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -2314,8 +2352,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); ; res = crosses.execute(polyline1, polyline2, sr, null); @@ -2329,8 +2367,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -2340,8 +2378,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[10,11],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -2387,10 +2425,12 @@ public static void testPolygonEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2407,10 +2447,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2431,10 +2473,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2456,10 +2500,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2483,10 +2529,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2508,10 +2556,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2529,10 +2579,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2554,10 +2606,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2579,10 +2633,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2605,10 +2661,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2629,10 +2687,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2653,10 +2713,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2683,10 +2745,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2713,10 +2777,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2739,10 +2805,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2762,10 +2830,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2808,11 +2878,13 @@ public static void testPolylineEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2830,11 +2902,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-10,0],[0,10]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-10,0],[0,10]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2848,11 +2921,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-11,0],[1,12]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-11,0],[1,12]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2866,11 +2940,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[6,6]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2885,11 +2960,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[10,10]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[10,10]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2902,11 +2978,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-5,5],[15,5]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-5,5],[15,5]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2920,11 +2997,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[5,15]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[5,15]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2945,11 +3023,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,11],[5,15]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,11],[5,15]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2965,11 +3044,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2988,11 +3069,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3005,11 +3088,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 511); wiggleGeometry(envelope, 0.00000001, 1982); @@ -3029,11 +3114,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3052,11 +3139,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,-2],[2,2]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,-2],[2,2]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3072,11 +3160,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,0],[2,2]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3092,11 +3181,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,0],[2,2]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3113,11 +3203,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[6,6]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3132,11 +3223,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[5,10]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[5,10]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3175,9 +3267,11 @@ public static void testMultiPointEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3193,9 +3287,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3215,9 +3311,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3236,9 +3334,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3255,9 +3355,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3272,9 +3374,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3299,9 +3403,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3326,9 +3432,10 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,10],[10,10]]}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,10],[10,10]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3357,9 +3464,10 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[1,10],[9,10]]}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[1,10],[9,10]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3387,9 +3495,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3404,9 +3514,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3421,9 +3533,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3439,9 +3553,10 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,-1]]}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,-1]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3477,9 +3592,10 @@ public static void testPointEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Point point = (Point) (importFromJson("{\"x\":5,\"y\":6}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":5,\"y\":6}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3493,9 +3609,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":10}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":10}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3509,9 +3626,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":11}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":11}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3525,9 +3643,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3541,9 +3660,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":5,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":5,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3557,9 +3677,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":11,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":11,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3573,9 +3694,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3609,9 +3731,11 @@ public static void testEnvelopeEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3628,9 +3752,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3646,9 +3772,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3664,9 +3792,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3682,9 +3812,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3700,9 +3832,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3718,9 +3852,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3736,9 +3872,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3754,9 +3892,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3772,9 +3912,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3790,9 +3932,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3808,9 +3952,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3826,9 +3972,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3844,9 +3992,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3862,9 +4012,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3880,9 +4032,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3898,9 +4052,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3916,9 +4072,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3934,9 +4092,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3952,9 +4112,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3971,9 +4133,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3990,9 +4154,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4009,9 +4175,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4028,9 +4196,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4047,9 +4217,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4066,9 +4238,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4349,6 +4523,43 @@ public static void testPolylinePolylineRelate() { scl = "***T*****"; res = op.execute(polyline1, polyline2, sr, scl, null); assertTrue(res); + + 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 + + polyline2.startPath(3, 3); + polyline2.lineTo(5, 5); + + op.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF1FFF102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); + + polyline2.startPath(8, 1); + polyline2.lineTo(8, 2); + + op.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF1FF0102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); } @Test @@ -4429,6 +4640,30 @@ public static void testPolygonPolylineRelate() { 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); } @Test @@ -4525,6 +4760,11 @@ public static void testPointPointRelate() { scl = "T********"; res = op.execute(p1, p2, sr, scl, null); assertTrue(res); + + p1.setXY(0, 0); + p2.setXY(1, 0); + res = op.execute(p1, p2, null, scl, null); + assertTrue(!res); } @Test @@ -4569,6 +4809,42 @@ public static void testPolygonMultiPointRelate() { scl = "TFF0F10FT"; res = op.execute(polygon1, multipoint2, sr, scl, null); assertTrue(!res); + + polygon1.setEmpty(); + multipoint2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 20); + polygon1.lineTo(20, 20); + polygon1.lineTo(20, 0); + + multipoint2.add(3, 3); + multipoint2.add(5, 5); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "TF2FF****"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + multipoint2.setEmpty(); + + polygon1.startPath(4, 0); + polygon1.lineTo(0, 4); + polygon1.lineTo(4, 8); + polygon1.lineTo(8, 4); + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF2FF10F2"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); } @Test @@ -4677,24 +4953,17 @@ public static void testPolylinePointRelate() { point.setXY(0, 3); - scl = "0F1FF0FF2'"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); } - static MapGeometry importFromJson(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); - } catch (Exception ex) { - } - - return null; + @Test + public static void testCrosses_github_issue_40() { + // Issue 40: Acceleration without a spatial reference changes the result of relation operators + Geometry geom1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); + Geometry geom2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", null); + boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, null); + assertTrue(answer1); + OperatorCrosses.local().accelerateGeometry(geom1, null, GeometryAccelerationDegree.enumHot); + boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, null); + assertTrue(answer2); } } diff --git a/src/test/java/com/esri/core/geometry/TestShapePreserving.java b/src/test/java/com/esri/core/geometry/TestShapePreserving.java deleted file mode 100644 index 0d5524e5..00000000 --- a/src/test/java/com/esri/core/geometry/TestShapePreserving.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestShapePreserving extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testBoxAreasWithinToleranceCR186615() { - /* - * //TODO: Implement these OperatorShapePreservingArea shapeAreaOp = - * OperatorShapePreservingArea.local(); OperatorShapePreservingLength - * shapeLengthOp = OperatorShapePreservingLength.local(); - * - * Polyline polyline2 = new Polyline(); polyline2.startPath(190, 0); - * polyline2.lineTo(200,0); SpatialReference spatialRefWGS = - * SpatialReference.create(4326); double lengthEquator10Degree = - * shapeLengthOp.execute(polyline2, spatialRefWGS, null); - * assertTrue(lengthEquator10Degree != 0.00); - * - * Polyline polylineEquator2 = new Polyline(); - * polylineEquator2.startPath(170, 0); polylineEquator2.lineTo(180,0); - * double lengthEquator10Degree2 = - * shapeLengthOp.execute(polylineEquator2, spatialRefWGS, null); - * assertTrue(GeomCommonMethods.compareDouble(lengthEquator10Degree2, - * lengthEquator10Degree, Math.pow(10.0,-10))); - * - * SpatialReference spatialRefWGSMerc = SpatialReference.create(102100); - * double PCS5 = 111319.49079327358 * 5; double PCS180 = - * 20037508.342789244; double CSYMax = 30240970.0; double CSYMin = - * -30240970.0; - * - * Polyline polylineEquator3 = new Polyline(); - * polylineEquator3.startPath(-PCS180 - 4*PCS5, 0); - * polylineEquator3.lineTo(-PCS180 - 2*PCS5, 0); double - * lengthEquatorMercDegree = shapeLengthOp.execute(*polylineEquator3, - * spatialRefWGSMerc, null); - * assertTrue(GeomCommonMethods.compareDouble(lengthEquatorMercDegree, - * lengthEquator10Degree, Math.pow(10.0,-10))); - * - * Polyline polylineBox = new Polyline(); polylineBox.startPath(PCS180 - - * 2*PCS5, 30240970.0 / 9); polylineBox.lineTo(PCS180 + 2*PCS5, - * 30240970.0 / 9); polylineBox.lineTo(PCS180 + 2*PCS5, -30240970.0 / - * 9); polylineBox.lineTo(PCS180 - 2*PCS5, -30240970.0 / 9); - * polylineBox.lineTo(PCS180 - 2*PCS5, 30240970.0 / 9); - * - * Polygon polygonBox = new Polygon(); polygonBox.startPath(PCS180 - - * 2*PCS5, 30240970.0 / 9); polygonBox.lineTo(PCS180 + 2*PCS5, - * 30240970.0 / 9); polygonBox.lineTo(PCS180 + 2*PCS5, -30240970.0 / 9); - * polygonBox.lineTo(PCS180 - 2*PCS5, -30240970.0 / 9); - * polygonBox.lineTo(PCS180 - 2*PCS5, 30240970.0 / 9); - * - * Envelope envelopeBox = new Envelope(); - * polygonBox.queryEnvelope(envelopeBox); - * - * double lengthBox1 = shapeLengthOp.execute(polylineBox, - * spatialRefWGSMerc, null); double lengthBox2 = - * shapeLengthOp.execute(polygonBox, spatialRefWGSMerc, null); double - * lengthBox3 = shapeLengthOp.execute(envelopeBox, spatialRefWGSMerc, - * null); assertTrue(GeomCommonMethods.compareDouble(lengthBox1, - * lengthBox2, Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(lengthBox1, lengthBox3, - * Math.pow(10.0,-10))); - * - * // Repeated polygon area Polygon polygonBox1 = new Polygon(); - * polygonBox1.startPath(-PCS180 - 6 * PCS5, 30240970.0 / 9); - * polygonBox1.lineTo(-PCS180 - 4 * PCS5, 30240970.0 / 9); - * polygonBox1.lineTo(-PCS180 - 4 * PCS5, -30240970.0 / 9); - * polygonBox1.lineTo(-PCS180 - 6 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox2 = new Polygon(); polygonBox2.startPath(-PCS180 - - * 2 * PCS5, 30240970.0 / 9); polygonBox2.lineTo(-PCS180, 30240970.0 / - * 9); polygonBox2.lineTo(-PCS180, -30240970.0 / 9); - * polygonBox2.lineTo(-PCS180 - 2 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox3 = new Polygon(); polygonBox3.startPath(-PCS180 - - * PCS5, 30240970.0 / 9); polygonBox3.lineTo(-PCS180 + PCS5, 30240970.0 - * / 9); polygonBox3.lineTo(-PCS180 + PCS5, -30240970.0 / 9); - * polygonBox3.lineTo(-PCS180 - PCS5, -30240970.0 / 9); - * - * Polygon polygonBox4 = new Polygon(); polygonBox4.startPath(-PCS180, - * 30240970.0 / 9); polygonBox4.lineTo(-PCS180 + 2 * PCS5, 30240970.0 / - * 9); polygonBox4.lineTo(-PCS180 + 2 * PCS5, -30240970.0 / 9); - * polygonBox4.lineTo(-PCS180, -30240970.0 / 9); - * - * Polygon polygonBox5 = new Polygon(); polygonBox5.startPath(PCS180 - 6 - * * PCS5, 30240970.0 / 9); polygonBox5.lineTo(PCS180 - 4 * PCS5, - * 30240970.0 / 9); polygonBox5.lineTo(PCS180 - 4 * PCS5, -30240970.0 / - * 9); polygonBox5.lineTo(PCS180 - 6 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox6 = new Polygon(); polygonBox6.startPath(PCS180 - 2 - * * PCS5, 30240970.0 / 9); polygonBox6.lineTo(PCS180, 30240970.0 / 9); - * polygonBox6.lineTo(PCS180, -30240970.0 / 9); - * polygonBox6.lineTo(PCS180 - 2 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox7 = new Polygon(); polygonBox7.startPath(PCS180 - - * PCS5, 30240970.0 / 9); polygonBox7.lineTo(PCS180 + PCS5, 30240970.0 / - * 9); polygonBox7.lineTo(PCS180 + PCS5, -30240970.0 / 9); - * polygonBox7.lineTo(PCS180 - PCS5, -30240970.0 / 9); - * - * Polygon polygonBox8 = new Polygon(); polygonBox8.startPath(PCS180, - * 30240970.0 / 9); polygonBox8.lineTo(PCS180 + 2 * PCS5, 30240970.0 / - * 9); polygonBox8.lineTo(PCS180 + 2 * PCS5, -30240970.0 / 9); - * polygonBox8.lineTo(PCS180, -30240970.0 / 9); - * - * Polygon polygonBox9 = new Polygon(); polygonBox9.startPath(PCS180 + 2 - * * PCS5, 30240970.0 / 9); polygonBox9.lineTo(PCS180 + 4 * PCS5, - * 30240970.0 / 9); polygonBox9.lineTo(PCS180 + 4 * PCS5, -30240970.0 / - * 9); polygonBox9.lineTo(PCS180 + 2 * PCS5, -30240970.0 / 9); - * - * double area1 = shapeAreaOp.execute(polygonBox1, spatialRefWGSMerc, - * null); double area2 = shapeAreaOp.execute(polygonBox2, - * spatialRefWGSMerc, null); double area3 = - * shapeAreaOp.execute(polygonBox3, spatialRefWGSMerc, null); double - * area4 = shapeAreaOp.execute(polygonBox4, spatialRefWGSMerc, null); - * double area5 = shapeAreaOp.execute(polygonBox5, spatialRefWGSMerc, - * null); double area6 = shapeAreaOp.execute(polygonBox6, - * spatialRefWGSMerc, null); double area7 = - * shapeAreaOp.execute(polygonBox7, spatialRefWGSMerc, null); double - * area8 = shapeAreaOp.execute(polygonBox8, spatialRefWGSMerc, null); - * double area9 = shapeAreaOp.execute(polygonBox9, spatialRefWGSMerc, - * null); - * - * assertTrue(GeomCommonMethods.compareDouble(area1, area2, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area3, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area4, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area5, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area6, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area7, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area8, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area9, - * Math.pow(10.0,-10))); - */ - } -} diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index 8d76da1b..b7caf8a3 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -40,6 +40,10 @@ protected void tearDown() throws Exception { } 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); diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index 94f3ef86..ad06dbbc 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -17,7 +17,6 @@ protected void tearDown() throws Exception { @Test public static void testUnion() { Point pt = new Point(10, 20); - System.out.println(pt.getX()); Point pt2 = new Point(); pt2.setXY(10, 10); @@ -34,6 +33,5 @@ public static void testUnion() { GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); Geometry result = outputCursor.next(); - System.out.println(result); } } diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 656765d8..196e5e1e 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -48,8 +48,6 @@ public void testWKB() { // geom = operatorImport.execute(0, Geometry.Type.Polygon, // byteBuffer); String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - System.out.println(strPolygon1); - System.out.println(outputPolygon1); } catch (JsonParseException ex) { } catch (IOException ex) { } diff --git a/src/test/java/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java index a984cfc5..9bbe51c0 100644 --- a/src/test/java/com/esri/core/geometry/Utils.java +++ b/src/test/java/com/esri/core/geometry/Utils.java @@ -2,6 +2,8 @@ public class Utils { static void showProjectedGeometryInfo(MapGeometry mapGeom) { + return; + /* System.out.println("\n"); MapGeometry geom = mapGeom; int wkid = geom.getSpatialReference() != null ? geom @@ -66,7 +68,7 @@ static void showProjectedGeometryInfo(MapGeometry mapGeom) { } System.out.println("wkid: " + wkid); - } + }*/ } From c171c80a46377b523fe6002eb550f438d8757bdc Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 24 Jul 2014 12:44:26 -0700 Subject: [PATCH 021/145] don't use Integer.compare for backward compatibility --- src/main/java/com/esri/core/geometry/Simplificator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 5bfaf014..95cf4f6c 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -1002,8 +1002,9 @@ int _compareVerticesSimple(int v1, int v2) { m_shape.getXY(v2, pt2); int res = pt1.compare(pt2); if (res == 0) {// sort equal vertices by the path ID - res = Integer.compare(m_shape.getPathFromVertex(v1), - m_shape.getPathFromVertex(v2)); + int i1 = m_shape.getPathFromVertex(v1); + int i2 = m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); } return res; From f264a50ab2c409fd5edf9135131bcf887f5822ba Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 21 Aug 2014 10:13:46 -0700 Subject: [PATCH 022/145] additional fixes in Simplify and Cut --- build.xml | 2 +- .../java/com/esri/core/geometry/Cracker.java | 13 - .../com/esri/core/geometry/EditShape.java | 12 +- .../geometry/OperatorIntersectionCursor.java | 7 +- .../core/geometry/RelationalOperations.java | 40 +- .../com/esri/core/geometry/Simplificator.java | 105 ++- .../geometry/StridedIndexTypeCollection.java | 14 +- .../com/esri/core/geometry/TopoGraph.java | 104 +-- .../com/esri/core/geometry/TestRelation.java | 613 +++++++++--------- 9 files changed, 462 insertions(+), 448 deletions(-) diff --git a/build.xml b/build.xml index 633710cf..14f0f909 100644 --- a/build.xml +++ b/build.xml @@ -57,7 +57,7 @@ - + diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index 6f320b04..98b4a909 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -23,7 +23,6 @@ */ package com.esri.core.geometry; -import java.util.Arrays; /** * Implementation for the segment cracking. @@ -33,11 +32,9 @@ */ class Cracker { private EditShape m_shape; - private Envelope2D m_extent; private ProgressTracker m_progress_tracker; private NonSimpleResult m_non_simple_result; private double m_tolerance; - private double m_tolerance_cluster; private Treap m_sweep_structure; private SweepComparator m_sweep_comparator; private boolean m_bAllowCoincident; @@ -51,7 +48,6 @@ boolean crackBruteForce_() { seg_1_env.setEmpty(); Envelope2D seg_2_env = new Envelope2D(); seg_2_env.setEmpty(); - boolean b_needs_filter = false; boolean assume_intersecting = false; Point helper_point = new Point(); SegmentIntersector segment_intersector = new SegmentIntersector(); @@ -80,7 +76,6 @@ boolean crackBruteForce_() { if (seg_1.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - b_needs_filter = true; continue; } } @@ -108,7 +103,6 @@ boolean crackBruteForce_() { if (seg_2.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - b_needs_filter = true; continue; } } @@ -192,7 +186,6 @@ boolean crackBruteForce_() { // degenerate // segments { - b_needs_filter = true; break; } } @@ -442,8 +435,6 @@ static boolean execute(EditShape shape, Envelope2D extent, Cracker cracker = new Cracker(progress_tracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; - cracker.m_tolerance_cluster = -1; - cracker.m_extent = extent; // Use brute force for smaller shapes, and a planesweep for bigger // shapes. boolean b_cracked = false; @@ -469,11 +460,9 @@ static boolean needsCracking(boolean allowCoincident, EditShape shape, double to if (!canBeCracked(shape)) return false; - Envelope2D shape_env = shape.getEnvelope2D(); Cracker cracker = new Cracker(progress_tracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; - cracker.m_extent = shape_env; cracker.m_bAllowCoincident = allowCoincident; if (cracker.needsCrackingImpl_()) { if (result != null) @@ -485,12 +474,10 @@ static boolean needsCracking(boolean allowCoincident, EditShape shape, double to Transformation2D transform = new Transformation2D(); transform.setSwapCoordinates(); shape.applyTransformation(transform); - transform.transform(shape_env); cracker = new Cracker(progress_tracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; - cracker.m_extent = shape_env; cracker.m_bAllowCoincident = allowCoincident; boolean b_res = cracker.needsCrackingImpl_(); diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 23f70df5..84557893 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -1475,11 +1475,12 @@ int insertClosedPath_(int geometry, int before_path, int first_vertex, int check int path = insertPath(geometry, -1); int path_size = 0; int vertex = first_vertex; - contains_checked_vertex[0] = false; + boolean contains = false; + while(true) { if (vertex == checked_vertex) - contains_checked_vertex[0] = true; + contains = true; setPathToVertex_(vertex, path); path_size++; @@ -1493,12 +1494,17 @@ int insertClosedPath_(int geometry, int before_path, int first_vertex, int check setClosedPath(path, true); setPathSize_(path, path_size); - if (contains_checked_vertex[0]) + if (contains) first_vertex = checked_vertex; setFirstVertex_(path, first_vertex); setLastVertex_(path, getPrevVertex(first_vertex)); setRingAreaValid_(path, false); + + if (contains_checked_vertex != null) { + contains_checked_vertex[0] = contains; + } + return path; } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index e06670dc..7706d83e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -606,8 +606,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { resSeg.getStartXY(), tolerance) != 1) { if (analyseClipSegment_(polygon, resSeg, tolerance) != 1) { - assert (false);// something went wrong. - return null; + return null; //someting went wrong we'll falback to slower but robust planesweep code. } } @@ -667,9 +666,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { // be // inside. if (clipStatus < 0) { - assert (clipStatus >= 0);// E-mail the repro case to - // the Geometry team to - // investigate. + assert (clipStatus >= 0); return null;// something goes wrong, resort to // planesweep } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index e88409ff..08d60ca3 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -894,7 +894,15 @@ private static boolean polygonEqualsPolygon_(Polygon polygon_a, progress_tracker)) return true; - return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance); + double length_a = polygon_a.calculateLength2D(); + double length_b = polygon_b.calculateLength2D(); + int max_vertices = Math.max(polygon_a.getPointCount(), + polygon_b.getPointCount()); + + if (Math.abs(length_a - length_b) > max_vertices * 4.0 * tolerance) + return false; + + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); } // Returns true if polygon_a is disjoint from polygon_b. @@ -1264,7 +1272,7 @@ private static boolean polygonEqualsEnvelope_(Polygon polygon_a, Polygon polygon_b = new Polygon(); polygon_b.addEnvelope(envelope_b, false); - return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance); + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); } // Returns true if polygon_a is disjoint from envelope_b. @@ -1520,7 +1528,7 @@ private static boolean polylineEqualsPolyline_(Polyline polyline_a, progress_tracker)) return true; - return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance); + return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance, false); } // Returns true if polyline_a is disjoint from polyline_b. @@ -1633,7 +1641,7 @@ private static boolean polylineContainsPolyline_(Polyline polyline_a, false) == Relation.disjoint) return false; - return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance); + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); } // Returns true if polyline_a is disjoint from point_b. @@ -2127,7 +2135,7 @@ private static boolean polylineContainsEnvelope_(Polyline polyline_a, polyline_b.startPath(p); envelope_b.queryCornerByVal(2, p); polyline_b.lineTo(p); - return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance); + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); } // Returns true if polyline_a crosses envelope_b. @@ -3405,16 +3413,16 @@ private static boolean multiPointIntersectsMultiPoint_( // Returns true if multipathA equals multipathB. private static boolean linearPathEqualsLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance) { - return linearPathWithinLinearPath_(multipathA, multipathB, tolerance) + MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { + return linearPathWithinLinearPath_(multipathA, multipathB, tolerance, bEnforceOrientation) && linearPathWithinLinearPath_(multipathB, multipathA, - tolerance); + tolerance, bEnforceOrientation); } // Returns true if the segments of multipathA are within the segments of // multipathB. private static boolean linearPathWithinLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance) { + MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { boolean bWithin = true; double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; @@ -3485,7 +3493,7 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, int result = segmentA.intersect(segmentB, null, scalarsA, scalarsB, tolerance); - if (result == 2) { + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { double scalar_a_0 = scalarsA[0]; double scalar_a_1 = scalarsA[1]; double scalar_b_0 = scalarsB[0]; @@ -3515,7 +3523,7 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, result = segmentA.intersect(segmentB, null, scalarsA, scalarsB, tolerance); - if (result == 2) { + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { scalar_a_0 = scalarsA[0]; scalar_a_1 = scalarsA[1]; @@ -3533,7 +3541,7 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, null, scalarsA, scalarsB, tolerance); - if (result == 2) { + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { scalar_a_0 = scalarsA[0]; scalar_a_1 = scalarsA[1]; @@ -3642,15 +3650,15 @@ private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, if (bIntAExtB && !bIntBExtA) return !linearPathWithinLinearPath_(multipathB, multipathA, - tolerance); + tolerance, false); if (bIntBExtA && !bIntAExtB) return !linearPathWithinLinearPath_(multipathA, multipathB, - tolerance); + tolerance, false); - return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance) + return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance, false) && !linearPathWithinLinearPath_(multipathB, multipathA, - tolerance); + tolerance, false); } // Returns true the dimension of intersection of _multipathA and diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 95cf4f6c..955fea71 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -46,7 +46,7 @@ class Simplificator { private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, m_userIndexSortedIndexToVertex); - // _ASSERT(m_sortedVertices.getData(vertexlistIndex) != 0xdeadbeef); + if (m_nextVertexToProcess == vertexlistIndex) { m_nextVertexToProcess = m_sortedVertices .getNext(m_nextVertexToProcess); @@ -65,12 +65,26 @@ private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int first = m_shape.getFirstVertex(path); if (first == vertex) { int next = m_shape.getNextVertex(vertex); - if (next != vertex) - m_shape.setFirstVertex_(path, next); - else { - m_shape.setFirstVertex_(path, -1); - m_shape.setLastVertex_(path, -1); + if (next != vertex) { + int p = m_shape.getPathFromVertex(next); + if (p == path) { + m_shape.setFirstVertex_(path, next); + return; + } + else { + int prev = m_shape.getPrevVertex(vertex); + if (prev != vertex) { + p = m_shape.getPathFromVertex(prev); + if (p == path) { + m_shape.setFirstVertex_(path, prev); + return; + } + } + } } + + m_shape.setFirstVertex_(path, -1); + m_shape.setLastVertex_(path, -1); } } } @@ -709,19 +723,17 @@ private void _resolveOverlapOddEven(boolean bDirection1, m_shape.removeVertexInternal_(vertexA2, true); _removeAngleSortInfo(vertexA1); _transferVertexData(vertexB2, vertexB1); - _beforeRemoveVertex(vertexB2, false); + _beforeRemoveVertex(vertexB2, true); m_shape.removeVertexInternal_(vertexB2, false); _removeAngleSortInfo(vertexB1); } else { - // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); - // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); m_shape.setNextVertex_(vertexA2, vertexA1); // B1 B2< m_shape.setPrevVertex_(vertexA1, vertexA2); // | | m_shape.setNextVertex_(vertexB1, vertexB2); // | | m_shape.setPrevVertex_(vertexB2, vertexB1); // A1< A2 _transferVertexData(vertexA2, vertexA1); - _beforeRemoveVertex(vertexA2, false); + _beforeRemoveVertex(vertexA2, true); m_shape.removeVertexInternal_(vertexA2, false); _removeAngleSortInfo(vertexA1); _transferVertexData(vertexB2, vertexB1); @@ -750,17 +762,17 @@ private void _resolveOverlapOddEven(boolean bDirection1, // m_shape.dbgVerifyIntegrity(a2);//debug boolean bVisitedA1 = false; - m_shape.setNextVertex_(a1, a2); // ^ | <--- ^ \ --. - m_shape.setNextVertex_(a2, a1); // | | | ^ | | - m_shape.setPrevVertex_(b1, b2); // | | | | | | - m_shape.setPrevVertex_(b2, b1); // | | | | | | - int v = b2; // | | | | =>| | - while (v != a2) // | | . | | -. | - { // | <-- | | | ./ | | - int prev = m_shape.getPrevVertex(v); // | | | | | | | | - int next = m_shape.getNextVertex(v); // <-+---<--- | - // <-+---<--- | - m_shape.setPrevVertex_(v, next); // --------. <-------- + m_shape.setNextVertex_(a1, a2); + m_shape.setNextVertex_(a2, a1); + m_shape.setPrevVertex_(b1, b2); + m_shape.setPrevVertex_(b2, b1); + int v = b2; + while (v != a2) + { + int prev = m_shape.getPrevVertex(v); + int next = m_shape.getNextVertex(v); + + m_shape.setPrevVertex_(v, next); m_shape.setNextVertex_(v, prev); bVisitedA1 |= v == a1; v = next; @@ -791,12 +803,6 @@ private void _resolveOverlapOddEven(boolean bDirection1, // m_shape.dbgVerifyIntegrity(b1);//debug // m_shape.dbgVerifyIntegrity(a1);//debug } - // else - // { - // m_shape._ReverseRingInternal(vertexA2); - // _ResolveOverlapOddEven(bDirection1, !bDirection2, vertexA1, - // vertexB1, vertexA2, vertexB2); - // } } } @@ -902,12 +908,10 @@ private void _fixOrphanVertices() { int pathSize = 1; for (int vertex = m_shape.getNextVertex(first); vertex != first; vertex = m_shape .getNextVertex(vertex)) { - // _ASSERT(m_shape.getPathFromVertex(vertex) == -1); m_shape.setPathToVertex_(vertex, path); - // _ASSERT(m_shape.getNextVertex(m_shape.getPrevVertex(vertex)) - // == vertex); pathSize++; } + m_shape.setRingAreaValid_(path,false); m_shape.setPathSize_(path, pathSize); m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); geometrySize += pathSize; @@ -915,40 +919,29 @@ private void _fixOrphanVertices() { path = m_shape.getNextPath(path); } - // produce new paths for the orphan vertices. + // Some vertices do not belong to any path. We have to create new path + // objects for those. + // Produce new paths for the orphan vertices. for (int node = m_sortedVertices.getFirst(m_sortedVertices .getFirstList()); node != -1; node = m_sortedVertices .getNext(node)) { int vertex = m_sortedVertices.getData(node); if (m_shape.getPathFromVertex(vertex) != -1) continue; - int path = m_shape.insertPath(m_geometry, -1); - int pathSize = 0; - int first = vertex; - while (true) { - m_shape.setPathToVertex_(vertex, path); - pathSize++; - int next = m_shape.getNextVertex(vertex); - // _ASSERT(m_shape.getNextVertex(m_shape.getPrevVertex(vertex)) - // == vertex); - if (next == first) - break; - vertex = next; - } - - m_shape.setClosedPath(path, true); - - m_shape.setPathSize_(path, pathSize); - m_shape.setFirstVertex_(path, first); - m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); - geometrySize += pathSize; + + int path = m_shape.insertClosedPath_(m_geometry, -1, vertex, vertex, null); + geometrySize += m_shape.getPathSize(path); pathCount++; } + m_shape.setGeometryPathCount_(m_geometry, pathCount); - int totalPointCount = m_shape.getTotalPointCount() - - m_shape.getPointCount(m_geometry); m_shape.setGeometryVertexCount_(m_geometry, geometrySize); - m_shape.setTotalPointCount_(totalPointCount + geometrySize); + int totalPointCount = 0; + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape.getNextGeometry(geometry)) { + totalPointCount += m_shape.getPointCount(geometry); + } + + m_shape.setTotalPointCount_(totalPointCount); } private int _getNextEdgeIndex(int indexIn) { @@ -1003,8 +996,8 @@ int _compareVerticesSimple(int v1, int v2) { 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); + int i2 = m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); } return res; diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 93de656f..785e696f 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -63,8 +63,13 @@ final class StridedIndexTypeCollection { m_blockSize = m_realBlockSize / m_realStride; } + private boolean dbgdelete_(int element) { + m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] = -0x7eadbeed; + return true; + } + void deleteElement(int element) { - assert dbgdelete_(element); + assert(dbgdelete_(element)); int totalStrides = (element >> m_blockPower) * m_blockSize * m_realStride + (element & m_blockMask); if (totalStrides < m_last * m_realStride) { @@ -79,12 +84,14 @@ void deleteElement(int element) { // Returns the given field of the element. int getField(int element, int field) { + assert(m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); return m_buffer[element >> m_blockPower][(element & m_blockMask) + field]; } // Sets the given field of the element. void setField(int element, int field, int value) { + assert(m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); m_buffer[element >> m_blockPower][(element & m_blockMask) + field] = value; } @@ -201,11 +208,6 @@ static boolean isValidElement(int element) { return element >= 0; } - private boolean dbgdelete_(int element) { - setField(element, 1, 0x7eadbeed); - return true; - } - private void ensureBufferBlocksCapacity(int blocks) { if (m_buffer.length < blocks) { int[][] newBuffer = new int[blocks][]; diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index affcbc60..0bf61cd7 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -1502,58 +1502,78 @@ void simplifyWinding_() { //there is nothing to do } + private int getFirstUnvisitedHalfEdgeOnCluster_(int cluster, int hintEdge, + int vistiedEdgesIndex) { + // finds first half edge which is unvisited (index is not set to 1. + // when hintEdge != -1, it is used to start going around the edges. + + int edge = hintEdge != -1 ? hintEdge : getClusterHalfEdge(cluster); + if (edge == -1) + return -1; + + int f = edge; + + while (true) { + int v = getHalfEdgeUserIndex(edge, vistiedEdgesIndex); + if (v != 1) { + return edge; + } + + int next = getHalfEdgeNext(getHalfEdgeTwin(edge)); + if (next == f) + return -1; + + edge = next; + } + } + boolean removeSpikes_() { boolean removed = false; int visitedIndex = createUserIndexForHalfEdges(); for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - int firstHalfEdge = getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - - do { - int visited = getHalfEdgeUserIndex(half_edge, visitedIndex); - int halfEdgeNext = getHalfEdgeNext(getHalfEdgeTwin(half_edge)); - if (visited != 1) { - int faceHalfEdge = half_edge; - do { - int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); - int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); - int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); - // We first check whether faceHalfEdge corresponds to - // the starting segment with - // "faceHalfEdge != half_edge && faceHalfEdgePrev != half_edge" - // Otherwise if we deleted the first half edge, then - // there could be an infinite loop since the terminal - // condition wouldn't occur. - // if (faceHalfEdge != half_edge && faceHalfEdgeNext != - // half_edge && faceHalfEdgePrev == faceHalfEdgeTwin) - if (faceHalfEdge != half_edge - && faceHalfEdgePrev != half_edge - && faceHalfEdgePrev == faceHalfEdgeTwin) { - deleteEdgeInternal_(faceHalfEdge); - removed = true; - assert (faceHalfEdgeNext != faceHalfEdge); - assert (faceHalfEdgeNext != faceHalfEdgeTwin); - assert (faceHalfEdgeNext != faceHalfEdgePrev); - } else { - setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); - } - - faceHalfEdge = faceHalfEdgeNext; - } while (faceHalfEdge != half_edge); + int nextClusterEdge = -1; //a hint + while (true) { + int firstHalfEdge = getFirstUnvisitedHalfEdgeOnCluster_(cluster, nextClusterEdge, visitedIndex); + if (firstHalfEdge == -1) + break; + + nextClusterEdge = getHalfEdgeNext(getHalfEdgeTwin(firstHalfEdge)); + int faceHalfEdge = firstHalfEdge; + while (true) { + int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + if (faceHalfEdgePrev == faceHalfEdgeTwin) { - deleteEdgeInternal_(faceHalfEdge); + deleteEdgeInternal_(faceHalfEdge); //deletes the edge and its twin removed = true; + + if (nextClusterEdge == faceHalfEdge || nextClusterEdge == faceHalfEdgeTwin) + nextClusterEdge = -1; //deleted the hint edge + + if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { + firstHalfEdge = faceHalfEdgeNext; + if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { + //deleted all edges in a face + break; + } + + faceHalfEdge = faceHalfEdgeNext; + continue; + } + } + else { + setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); + } + + faceHalfEdge = faceHalfEdgeNext; + if (faceHalfEdge == firstHalfEdge) + break; } - half_edge = halfEdgeNext; - } while (half_edge != firstHalfEdge); + } } return removed; @@ -2104,7 +2124,9 @@ int deleteEdgeInternal_(int half_edge) { setChainHalfEdge_(chain, n); } - if (!NumberUtils.isNaN(getChainArea(chain))) { + int chainIndex = getChainIndex_(chain); + double v = m_chainAreas.read(chainIndex); + if (!NumberUtils.isNaN(v)) { setChainArea_(chain, NumberUtils.TheNaN); setChainPerimeter_(chain, NumberUtils.TheNaN); } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index e12c1d4a..613f8b13 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -2,13 +2,12 @@ import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; public class TestRelation extends TestCase { + @Override protected void setUp() throws Exception { super.setUp(); @@ -629,7 +628,7 @@ public static void testPolygonPolygonEquals() { // Polygon1 and Polygon2 are topologically equal, but have differing // number of vertices - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[0,5],[0,7],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str1 = "{\"rings\":[[[0,0],[0,5],[0,7],[0,10],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(str1) @@ -681,9 +680,20 @@ public static void testPolygonPolygonEquals() { polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(res); + assertTrue(!res); res = equals.execute(polygon2, polygon1, sr, null); - assertTrue(res); + assertTrue(!res); + + // The rings are equal but first polygon has two rings stacked + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(!res); } @Test @@ -2435,10 +2445,10 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(equals.execute(envelope, densified, sr, null)); // they - // cover - // the - // same - // space + // cover + // the + // same + // space assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2457,14 +2467,14 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // polygon - // contains - // the - // envelope, - // but - // they - // aren't - // equal + // polygon + // contains + // the + // envelope, + // but + // they + // aren't + // equal assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2483,15 +2493,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // sticks - // outside - // of - // the - // polygon - // but - // they - // intersect + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2510,17 +2520,17 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // sticks - // outside - // of - // the - // polygon - // but - // they - // intersect - // and - // overlap + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect + // and + // overlap assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2539,15 +2549,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // rides - // the - // side - // of - // the - // polygon - // (they - // touch) + // envelope + // rides + // the + // side + // of + // the + // polygon + // (they + // touch) assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -2566,12 +2576,12 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(contains.execute(densified, envelope, sr, null)); // polygon - // and - // envelope - // cover - // the - // same - // space + // and + // envelope + // cover + // the + // same + // space assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2590,15 +2600,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // sticks - // outside - // of - // polygon, - // but - // the - // envelopes - // are - // equal + // sticks + // outside + // of + // polygon, + // but + // the + // envelopes + // are + // equal assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2617,15 +2627,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // the - // polygon - // envelope - // doesn't - // contain - // the - // envelope, - // but - // they - // intersect + // polygon + // envelope + // doesn't + // contain + // the + // envelope, + // but + // they + // intersect assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2644,16 +2654,16 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // on - // border - // (i.e. - // touches) + // degenerate + // to + // a + // point + // and + // is + // on + // border + // (i.e. + // touches) assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2672,14 +2682,14 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // properly - // inside + // degenerate + // to + // a + // point + // and + // is + // properly + // inside assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2698,14 +2708,14 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // properly - // outside + // degenerate + // to + // a + // point + // and + // is + // properly + // outside assertTrue(disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2724,20 +2734,20 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line - // and - // rides - // the - // bottom - // of - // the - // polygon - // (no - // interior - // intersection) + // degenerate + // to + // a + // line + // and + // rides + // the + // bottom + // of + // the + // polygon + // (no + // interior + // intersection) assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2756,20 +2766,20 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // touches - // the - // border - // on - // the - // inside - // yet - // has - // interior - // intersection + // degenerate + // to + // a + // line, + // touches + // the + // border + // on + // the + // inside + // yet + // has + // interior + // intersection assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2788,16 +2798,16 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // touches - // the - // boundary, - // and - // is - // outside + // degenerate + // to + // a + // line, + // touches + // the + // boundary, + // and + // is + // outside assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2816,13 +2826,13 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // and - // is - // outside + // degenerate + // to + // a + // line, + // and + // is + // outside assertTrue(disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2841,13 +2851,13 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // and - // crosses - // polygon + // degenerate + // to + // a + // line, + // and + // crosses + // polygon assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2889,11 +2899,11 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // straddles - // the - // envelope - // like - // a hat + // straddles + // the + // envelope + // like + // a hat assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -2950,8 +2960,8 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside + // properly + // inside assertTrue(contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3007,14 +3017,14 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // slices - // through - // the - // envelope - // (interior - // and - // exterior - // intersection) + // slices + // through + // the + // envelope + // (interior + // and + // exterior + // intersection) assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3033,9 +3043,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // outside - // of - // envelope + // outside + // of + // envelope assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3055,12 +3065,12 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // straddles - // the - // degenerate - // envelope - // like - // a hat + // straddles + // the + // degenerate + // envelope + // like + // a hat assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3099,13 +3109,13 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 511); wiggleGeometry(envelope, 0.00000001, 1982); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // is at - // the - // end - // point - // of - // polyline + // envelope + // is at + // the + // end + // point + // of + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -3125,12 +3135,12 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // is at - // the - // interior - // of - // polyline + // envelope + // is at + // the + // interior + // of + // polyline assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3149,9 +3159,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // crosses - // polyline + // envelope + // crosses + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3170,9 +3180,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // crosses - // polyline + // envelope + // crosses + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -3191,9 +3201,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // contains - // polyline + // envelope + // contains + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); @@ -3213,8 +3223,8 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside + // properly + // inside assertTrue(!contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -3233,8 +3243,8 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside + // properly + // inside assertTrue(contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3276,9 +3286,9 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // on - // boundary + // points + // on + // boundary assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3296,13 +3306,13 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary - // and - // one - // point - // in - // interior + // on + // boundary + // and + // one + // point + // in + // interior assertTrue(contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3320,12 +3330,12 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary, - // one - // interior, - // one - // exterior + // on + // boundary, + // one + // interior, + // one + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3343,10 +3353,10 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary, - // one - // exterior + // on + // boundary, + // one + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3364,8 +3374,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3383,18 +3393,18 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelope - // slices - // through - // some - // points, - // but - // some - // points - // are - // off - // the - // line + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3412,18 +3422,18 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelope - // slices - // through - // some - // points, - // but - // some - // points - // are - // off - // the - // line + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3440,22 +3450,22 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelopes - // slices - // through - // all - // the - // points, - // and - // they - // are - // at - // the - // end - // points - // of - // the - // line + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // at + // the + // end + // points + // of + // the + // line assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3472,21 +3482,21 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelopes - // slices - // through - // all - // the - // points, - // and - // they - // are - // in - // the - // interior - // of - // the - // line + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // in + // the + // interior + // of + // the + // line assertTrue(contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3504,8 +3514,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3523,8 +3533,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3542,8 +3552,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(multi_point, envelope, sr, null)); assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); @@ -3561,8 +3571,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(contains.execute(multi_point, envelope, sr, null)); assertTrue(contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); @@ -4937,33 +4947,22 @@ public static void testMultiPointMultipointRelate() { assertTrue(res); } - @Test - public static void testPolylinePointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - 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); - - } - @Test public static void testCrosses_github_issue_40() { - // Issue 40: Acceleration without a spatial reference changes the result of relation operators - Geometry geom1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); - Geometry geom2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", null); - boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, null); + // Issue 40: Acceleration without a spatial reference changes the result + // of relation operators + Geometry geom1 = OperatorImportFromWkt.local().execute(0, + Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); + Geometry geom2 = OperatorImportFromWkt.local().execute(0, + Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", + null); + boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, + null); assertTrue(answer1); - OperatorCrosses.local().accelerateGeometry(geom1, null, GeometryAccelerationDegree.enumHot); - boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, null); + OperatorCrosses.local().accelerateGeometry(geom1, null, + GeometryAccelerationDegree.enumHot); + boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, + null); assertTrue(answer2); } } From 45be0d08dac0a04ae16057bfb295bfdfd35dcf08 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 3 Sep 2014 18:55:33 -0700 Subject: [PATCH 023/145] javadoc edits --- .../java/com/esri/core/geometry/Geometry.java | 22 +++-- .../core/geometry/GeometryAccelerators.java | 12 --- .../esri/core/geometry/GeometryEngine.java | 97 +++++++++++++++++-- .../java/com/esri/core/geometry/Operator.java | 12 ++- .../esri/core/geometry/OperatorSimplify.java | 49 ++++++++-- .../core/geometry/OperatorSimplifyOGC.java | 34 +++++-- 6 files changed, 180 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 08e15e40..d4cd9952 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -537,15 +537,25 @@ synchronized void _touch() { /** * Describes the degree of acceleration of the geometry. + * Acceleration usually builds a raster and a quadtree. */ static public enum GeometryAccelerationDegree { - enumMild, // m_path_envelopes; diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 66406448..048994dd 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -34,7 +34,10 @@ import org.json.JSONException; /** - * Provides services that operate on geometry instances. + * 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 { @@ -45,6 +48,8 @@ public class GeometryEngine { * 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). @@ -60,6 +65,8 @@ public static MapGeometry jsonToGeometry(JsonParser json) { * 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). @@ -76,6 +83,8 @@ public static MapGeometry jsonToGeometry(String json) throws JsonParseException, /** * Exports the specified geometry instance to it's JSON representation. * + * See OperatorExportToJson. + * * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, * Geometry geometry) * @param wkid @@ -94,6 +103,8 @@ public static String geometryToJson(int wkid, Geometry 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 @@ -118,6 +129,8 @@ public static String geometryToGeoJson(Geometry geometry) { /** * Exports the specified geometry instance to its GeoJSON representation. * + *See OperatorExportToGeoJson. + * * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, * Geometry geometry) * @@ -135,6 +148,8 @@ public static String geometryToGeoJson(int wkid, Geometry geometry) { /** * Exports the specified geometry instance to it's JSON representation. * + *See OperatorImportFromGeoJson. + * * @param spatialReference * The spatial reference of associated object. * @param geometry @@ -152,6 +167,8 @@ public static String geometryToGeoJson(SpatialReference spatialReference, /** * Imports geometry from the ESRI shape file format. * + * See OperatorImportFromESRIShape. + * * @param esriShapeBuffer * The buffer containing geometry in the ESRI shape file format. * @param geometryType @@ -180,6 +197,8 @@ public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, /** * 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. @@ -194,6 +213,9 @@ public static byte[] geometryToEsriShape(Geometry geometry) { /** * 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. @@ -210,6 +232,9 @@ public static Geometry geometryFromWkt(String wkt, int importFlags, /** * 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. @@ -226,6 +251,9 @@ public static MapGeometry geometryFromGeoJson(String geoJson, /** * 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. @@ -240,6 +268,8 @@ public static String geometryToWkt(Geometry geometry, int exportFlags) { * 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 @@ -262,6 +292,8 @@ public static Geometry union(Geometry[] geometries, * 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 @@ -282,6 +314,8 @@ public static Geometry difference(Geometry geometry1, Geometry substractor, /** * Creates the symmetric difference of two geometries. * + * See OperatorSymmetricDifference. + * * @param leftGeometry * is one of the Geometry instances in the XOR operation. * @param rightGeometry @@ -302,6 +336,8 @@ public static Geometry symmetricDifference(Geometry leftGeometry, /** * Indicates if two geometries are equal. * + * See OperatorEquals. + * * @param geometry1 * Geometry. * @param geometry2 @@ -319,6 +355,10 @@ public static boolean equals(Geometry geometry1, Geometry geometry2, return result; } + /** + * See OperatorDisjoint. + * + */ public static boolean disjoint(Geometry geometry1, Geometry geometry2, SpatialReference spatialReference) { OperatorDisjoint op = (OperatorDisjoint) factory @@ -332,6 +372,8 @@ public static boolean disjoint(Geometry geometry1, Geometry geometry2, * 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 @@ -362,6 +404,8 @@ static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, /** * Creates a geometry through intersection between two geometries. * + * See OperatorIntersection. + * * @param geometry1 * The first geometry. * @param intersector @@ -382,6 +426,8 @@ public static Geometry intersect(Geometry geometry1, Geometry intersector, /** * 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. @@ -404,6 +450,8 @@ public static boolean within(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry contains another geometry. * + * See OperatorContains. + * * @param geometry1 * The geometry that is tested for the contains relationship to * the other geometry.. @@ -426,6 +474,8 @@ public static boolean contains(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry crosses another geometry. * + * See OperatorCrosses. + * * @param geometry1 * The geometry to cross. * @param geometry2 @@ -446,6 +496,8 @@ public static boolean crosses(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry touches another geometry. * + * See OperatorTouches. + * * @param geometry1 * The geometry to touch. * @param geometry2 @@ -466,6 +518,8 @@ public static boolean touches(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry overlaps another geometry. * + * See OperatorOverlaps. + * * @param geometry1 * The geometry to overlap. * @param geometry2 @@ -486,6 +540,8 @@ public static boolean overlaps(Geometry geometry1, Geometry geometry2, /** * Indicates if the given relation holds for the two geometries. * + * See OperatorRelate. + * * @param geometry1 * The first geometry for the relation. * @param geometry2 @@ -508,6 +564,8 @@ public static boolean relate(Geometry geometry1, Geometry geometry2, /** * Calculates the 2D planar distance between two geometries. * + * See OperatorDistance. + * * @param geometry1 * Geometry. * @param geometry2 @@ -528,6 +586,8 @@ public static double distance(Geometry geometry1, Geometry geometry2, /** * Calculates the clipped geometry from a target geometry using an envelope. * + * See OperatorClip. + * * @param geometry * The geometry to be clipped. * @param envelope @@ -559,6 +619,8 @@ public static Geometry clip(Geometry geometry, Envelope envelope, * 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 @@ -585,19 +647,21 @@ public static Geometry[] cut(Geometry cuttee, Polyline cutter, } 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) { @@ -633,11 +697,14 @@ public static Polygon[] buffer(Geometry[] geometries, /** * 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; @@ -652,6 +719,8 @@ public static Polygon buffer(Geometry geometry, /** * Calculates the convex hull geometry. * + * See OperatorConvexHull. + * * @param geometry The input geometry. * @return Returns the convex hull. * @@ -676,6 +745,8 @@ public static Geometry convexHull(Geometry geometry) { /** * Calculates the convex hull. * + * See OperatorConvexHull + * * @param geometries * The input geometry array. * @param b_merge @@ -709,13 +780,15 @@ public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { /** * 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) { @@ -730,12 +803,14 @@ public static Proximity2DResult getNearestCoordinate(Geometry geometry, * 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 @@ -749,6 +824,8 @@ public static Proximity2DResult getNearestVertex(Geometry geometry, * 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 @@ -758,7 +835,7 @@ public static Proximity2DResult getNearestVertex(Geometry geometry, * @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 @@ -772,6 +849,8 @@ public static Proximity2DResult[] getNearestVertices(Geometry geometry, /** * Performs the simplify operation on the geometry. + * + * See OperatorSimplify and See OperatorSimplifyOGC. * * @param geometry * The geometry to be simplified. @@ -790,6 +869,8 @@ public static Geometry simplify(Geometry geometry, /** * Checks if the Geometry is simple. * + * See OperatorSimplify. + * * @param geometry * The geometry to be checked. * @param spatialReference diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 20c00787..41f69129 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -33,7 +33,7 @@ public abstract class Operator { * The operator type enum. */ public enum Type { - Project, // = 10300,// Date: Fri, 5 Sep 2014 11:32:08 -0700 Subject: [PATCH 024/145] add serializable to PointXX and EnvelopeXX --- src/main/java/com/esri/core/geometry/Envelope1D.java | 5 ++++- src/main/java/com/esri/core/geometry/Envelope2D.java | 1 + src/main/java/com/esri/core/geometry/Envelope3D.java | 6 +++++- .../esri/core/geometry/OperatorGeodeticDensifyLocal.java | 2 +- src/main/java/com/esri/core/geometry/Point2D.java | 6 ++++-- src/main/java/com/esri/core/geometry/Point3D.java | 6 +++++- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 6f3da52d..993eef36 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -25,10 +25,13 @@ package com.esri.core.geometry; +import java.io.Serializable; + /** * A 1-dimensional interval. */ -public final class Envelope1D { +public final class Envelope1D implements Serializable { + private static final long serialVersionUID = 1L; public double vmin; diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index a6641de9..1a3907a2 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -33,6 +33,7 @@ */ 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; diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 18fd6515..1dd38ef6 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -25,10 +25,14 @@ package com.esri.core.geometry; +import java.io.Serializable; + /** * A class that represents axis parallel 3D rectangle. */ -public final class Envelope3D { +public final class Envelope3D implements Serializable{ + private static final long serialVersionUID = 1L; + public double xmin; public double ymin; diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java index 5b81f8cf..011bc4b2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -25,7 +25,7 @@ package com.esri.core.geometry; //This is a stub -public class OperatorGeodeticDensifyLocal extends +class OperatorGeodeticDensifyLocal extends OperatorGeodeticDensifyByLength { @Override diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index cb673ff5..5525626b 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -25,6 +25,7 @@ package com.esri.core.geometry; +import java.io.Serializable; import java.math.BigDecimal; import java.util.Comparator; @@ -33,8 +34,9 @@ * Basic 2D point class. Contains only two double fields. * */ -public final class Point2D { - +public final class Point2D implements Serializable{ + private static final long serialVersionUID = 1L; + public double x; public double y; diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index f5d343ff..5f509019 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -25,12 +25,16 @@ package com.esri.core.geometry; +import java.io.Serializable; + /** * * Basic 3D point class. * */ -public final class Point3D { +public final class Point3D implements Serializable { + private static final long serialVersionUID = 1L; + public double x; public double y; public double z; From 9695562bee2d2410f604393047086abb86560def Mon Sep 17 00:00:00 2001 From: serg4066 Date: Mon, 8 Sep 2014 14:50:58 -0700 Subject: [PATCH 025/145] a javadoc fix for Envelope2D.intersects (issue #59) --- src/main/java/com/esri/core/geometry/Envelope2D.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 1a3907a2..f2e4ef41 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -272,8 +272,11 @@ public boolean isIntersectingNE(Envelope2D other) { } /** - * Checks if this envelope intersects the other. - * @return True if this envelope intersects the other. + * 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(Envelope2D other) { if (isEmpty() || other.isEmpty()) From 5324ae50d61d308708581b2a62233d16ff9d3bf5 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 10 Sep 2014 09:01:16 -0700 Subject: [PATCH 026/145] added developer info and updated distribution management --- pom.xml | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 8 deletions(-) mode change 100644 => 100755 pom.xml diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 24547bbc..f3e49e98 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.1.2-SNAPSHOT + 1.2 jar Esri Geometry API for Java @@ -19,12 +19,69 @@ + + + stolstov + Sergey Tolstov + Esri + http://www.esri.com + + developer + + + + abalog + Aaron Balog + Esri + http://www.esri.com + + developer + + + + scm:git:git@github.com:Esri/geometry-api-java.git scm:git:git@github.com:Esri/geometry-api-java.git git@github.com:Esri/geometry-api-java.git + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + UTF-8 @@ -42,13 +99,6 @@ 2.9 - - - org.sonatype.oss - oss-parent - 7 - - org.json @@ -125,6 +175,17 @@ + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + ossrh + https://oss.sonatype.org/ + true + + From 6005d9387158bb0ed4ad538611e7aa0e13c069e1 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 10 Sep 2014 09:26:26 -0700 Subject: [PATCH 027/145] updated to match an even more recent change to release deployment --- pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index f3e49e98..760d2734 100755 --- a/pom.xml +++ b/pom.xml @@ -48,13 +48,7 @@ - release-sign-artifacts - - - performRelease - true - - + release From 6c09743e80509d91f110af0fafb3fcd470a7c682 Mon Sep 17 00:00:00 2001 From: Michael Park Date: Wed, 10 Sep 2014 12:25:13 -0700 Subject: [PATCH 028/145] Update README.md Updated Maven dependency version to 1.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 618f1572..16d10710 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.1 + 1.2 ``` From eb7a0d105ab345b6a880a1a1d568aef4fcc97afa Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Mon, 27 Oct 2014 09:32:09 -0700 Subject: [PATCH 029/145] Make decimal separator locale independent for JSON --- src/main/java/com/esri/core/geometry/StringUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java index 6797d94e..f859e9d8 100644 --- a/src/main/java/com/esri/core/geometry/StringUtils.java +++ b/src/main/java/com/esri/core/geometry/StringUtils.java @@ -22,6 +22,7 @@ email: contracts@esri.com */ package com.esri.core.geometry; +import java.util.Locale; class StringUtils { @@ -35,7 +36,7 @@ static void appendDouble(double value, int precision, String format = "%." + precision + "g"; - String str_dbl = String.format(format, value); + String str_dbl = String.format(Locale.US, format, value); boolean b_found_dot = false; boolean b_found_exponent = false; @@ -69,7 +70,7 @@ static void appendDoubleF(double value, int decimals, String format = "%." + decimals + "f"; - String str_dbl = String.format(format, value); + String str_dbl = String.format(Locale.US, format, value); boolean b_found_dot = false; From ad5fb8451e49ce334e11f2f5789dba0d7963bb2f Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 11 Dec 2014 15:36:35 -0800 Subject: [PATCH 030/145] 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 031/145] 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 032/145] 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 033/145] 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 034/145] 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 035/145] 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 036/145] 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 037/145] 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 038/145] 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 039/145] 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 040/145] [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 041/145] [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 042/145] 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 043/145] [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 044/145] 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 045/145] [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 046/145] 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 047/145] 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 048/145] 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 049/145] 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 050/145] 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 051/145] 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 052/145] 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 053/145] 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 054/145] 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 055/145] 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 056/145] 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 057/145] 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 058/145] 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 059/145] 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 060/145] 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 061/145] 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 062/145] 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 063/145] 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 064/145] 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 065/145] 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 066/145] 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 067/145] 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 068/145] 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 069/145] 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 070/145] 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 071/145] 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 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�pcp09 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 z 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

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

?@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 072/145] 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 073/145] 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 074/145] 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 075/145] 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 076/145] 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 077/145] 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 078/145] 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 079/145] 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 zcmbrkW0YoHvM!pev~AnAZQHi(O53(=+p4skm9}l1>h3-E?(^N-eQw_~$Gd*KYp#en zB38^No)t6XrGP<@0l>k*0SdBd#Q^?kAOnB^$cQKl&`8LN(#Z(ON{EUmDbvb`evJbF zBrD6ruG7PJ6W!ub!=i{=B^tky3t+qYOBV6Z395NjN*e~aC9*f;@P7G_1r-NP%{xkQ zUr%*;;LZezdJiX!%t%wx5@Mwfk*DGk!Nr!(BkkUznBiixVWvDbTVPJbIKcf30>gFf zYE7CY9igjC$FclqYmG1Al=vp1(|7MnqVlMMJAD^qJdl5u2?w7mo=@no5dNJ{qOvJ6 z{V|H%&rnDZ>-|8FmG1~~OAo95i%=YTr?))#{oca5uz9(@;oK$n+@*oI2K;=RpEr>% z(czP&GDL^m$1tXSf^e9TlD>6=CZEA&B?|m>C6K!6Y+u2TJsYlG1iHZOZ73Hzn(Ngx zTTls8P`9+~%o~pu^k5nvKd}my(anHAhEYNiKR6>-;{d{+bc+vgGQkkEMf?Hzpi3%(Ur5gwNro1lS<{=-s#|I>7 zsDwU$$u}7UhT^9N#xG^zy**T&+MA_dX*T&q-j60AV@mxKAMZqs@T2nm)FelXBrg(n zuOi?y5OnB4Ys138sUM77u7w6{yRA0pc;ZksfpGg?{uJm#f*`maE2b4clkxV+p15`s zc6qxdN#NQ-98kxPgX(Qr8C+`>KNWd$9q1_8kpVi=wwyj&jv{5o;J?n z$xDHO8=C4ca{&PWWPt(z$p6a#g!mJT{~dt9e;xz_|66$ek^B4jAHnp$L@xZF`TrJ_ zmJkw=RT7bjW3U5afDV5CfQ*D7&Zy*UP(cwwC>V4(J(2QuLyZ$|iBCSY z|KMm4{B?-s2*(92HU;5&ISLqYz+a)kF9SZ4^@E&TavnNKe6~V4Rt}q-LL^O*7!Yf$ z8x@+UzhSC-io7Y{^LieWmUx_HZs|I`=5C4R*GS zWSND@`-|Izn_l>7n+g!@ncfh|nD6A)DegBX7(t!W$*}pmn?w=hbE2sR& zMJkFib{q82-RElXCnSK3rK=`=t{U+q{1Hu{N7$~#CY;NZkv~Y-9((bMiE%s=$bGv? zpWVS?&Gm@D+^h6*43Jxpi6R+V-OvE@-AcbE#OLdw(W$HT$%M~SeCM;gm56)H|2PwV7jXO|-Qe(u#z35L}3l;cp#35gG6jG*7Cnm-^t zWDZUN(Ohv!dfh^-XA}#WK8;#TypulhUgRR}b5LdsXBnRscf`?L`dPMEFr|)uWZiGj zyT-esm3>go$c2i|$c7dXEsTO`Syx))ky}`si6kWw6GmXP0>rssfwi}__i41lUbny9 ztz{-g*Sv!4%;&(L*B94-VYr+fnJ#jbQXcSEQ7Rw5ok{p7h8fS6vecqo22d(X=aYu5 z1OzCzf<~oCw}+-+)}xw`?nQ4Ar`&M1-{0e8Ql0PoPmHhv|8t!DH3j@{abjcOYGOE&6WM@NbYT)E-;^=N;O>1djWaVUMOKW83X!0LvX8Vsc|JO1!|M4;+ z|Nob<|6{1Vqn*8pqqBvH6U}%(WB@-r!0fy4@XzBPC@_d!pmPM^XJ8{PO;avr7%O$X zbOq=8CS%}w8WSq z>|b;N6Y%*AYp1ExW(7CIK@rxR(h=_Fkz@ZbU62~zur{FcM zn}`q8uq_H2126e9Bxf;{nYqWyDKZd4QL8#JJKkQgE?X+q!BZQQRcmOS^(5zp@yXv* zZ668eE^c5RWn)~KIOKGUylwtiOvx*}&+X=zrb1)fxO0^jO(|d@$l%K-xCc0pvc}yBxdod3=%9ucsJ&adc68SfknFJfKFM| z%61UFAzyI{iEqU$M^OP7$cTKx2$F|_#Cz6oV^@WVmKk)SMCB2u=;gI zEBR4umIVA>wN6(IICjACd;0=okh>3qO1qYR!eeV?e4r4@Wi4ejcKwdQ(UnQ74CRlk za$l3EAj4b;DM)M|OIV{Fl|1@v7GmH$ToqK(VEnDscG&mpd{ne*OvFI_w3hBVL5<>c z#YkO-jioYQyg@J3j3p}2muEz!t6j|~JvC%HXp#b*kGVF@Mh>aU@L+%v+=fFUd^i>- zG2Lu}{`R5Rdhl-A@#LXRfFUv-4Pyy2f^d^A37F2b&J||PG)}qa4ffDnO-Y)Y}oEc}o9Ms6j9@c-VbY=WA2s z6v3nvTu8syaAO-0C(?H)9(54F?&Gcfi@I1WXr{i^F$`==?u;Xl-P8PsQdw-dN^k{X ztoPFvSgOO3d^|rWEpOAW=gH3l=8xPdp+*M{NmW5jCK|4L#p@NNBD`2BZv?#LnH;ae zu~zEm>29CI8B6IvZM{#AfS?fCQ@yuKpYqu{P||5%TYXWn60UYDpY6EZ9V5|KHehz> zTAB-mjT}nRm+~1d0_Q)wPqShr*Yt7 zu1(N!gnf^A=cVC>i%M*)o2DJ2rZq(F&GB4JBfTU0nSkm}VCHgtb%IO1;4X(> zm51H37W20!M+67ukG`Dwy1M-vO7C)OnYGNjT%1Gxs?e=S+fuEPW0twf0*Vko`8t$K zN*pg5SZv{PgS;7U;0&{$?{Sg*W4L+(4B?LWzFX`VlX1bH%Zw{=G+%$`ZJPTT&2)d* z@DAcXL-St~>YvH~A+}B?j;6Y5r;s z$e-CiOaEH~{wf3eUo!tqBK{+3)c=R{e{qBVNDJ1#)bf917}b^g%s z{uUekhTPE`x>56gX2ZGmx&`E2yp4~Eb@|X4HNeSrcK|8mhIb~+M$XpeYLSTv%*2EkS52vHDpaCn8_68DtpsR|0d57Shs%jkZ`&qL zs{M`H!@fC-7W2q#ElyTgpQ8jJI?6vT-5lE4;XVaqhmaT4Y<&qs77YHm+uuBw*1jC| zSmXt9Hg8Uyp(|Pj6I05L+fdA++?c*&8|_vPUmT#g!S7d^zKMQyk!UzfWW7v}mAb+@ zi*zOR5TcPoZdBq`NO9D{ig;~z0$Vz7>R8uXYoNv6z=IOYBD^Qj*v}YCj40KpX)ASz zIDtPEpkA6ziX1!F;vQ9yc=xba4BfR?4P$ZCLYo!0H}>lmF$FAEr|8zTzKRfpxap&p z+=MzyQdF}_uJX}9-(eowE!(DK&j*bum!dxd`{6NrV=}BRW!b1?qo{ub*S=r~rE0g2 ze2O8*gwd@|#adoiTF9_Mc-4_a+HR6##1048b}gNiBn|Y5!Tq=T!u^lX_S%Uk`&sof zGmH>w(^oL+ge(2X)qL46gLS1T_33bP&ZVqzcu0tNZnQ|~yi>FL8YT-lQL4*Nw=A&izQ zS=2P0KlKu_AM=8Iy5(J#a%3#l_|kk4;MBlq*8!-%)G3Lp=$fqKFNn>oDu_8*tPM?3 z8I>SxZ79$&a?GPlh_FY&T9COFjKk^f%fh<-mie_Q3#2BK>uF)5!s^^BhA)on^}Iw) zYjx7r#DhDh;+&equc}x&(@QPe^~Bk79HPm6B4biifltl#0_#Ma7%{feA0yok^@pU3 z+J7}BRYESc$;l`W3DuL>tVGynKxI;qBrR}O19x2^Jlc+p32q3^I~la zYPWwur)PLicM|Z%MWvYun}FgeIEl6``H$gFSLheh=cuqT_e!%#W}_Gw4}c>_~CKj-x?|>=8 zR2C3}CZ7D`YPgQSjM1K>hW^H6(|Sksf`s~|so!8nfZI@>MTX7%M~|^STaWn;1-H!( z*FXplIx=_qf-E#b#biIo``QKMYN{Ik0BCeGQ=)(S587dqsyXdIpeO+{@Fucg54omj z%+)z8nM{9WLTm;n`anWvV5lD~85XnD8B;a25OvuNWTptHV0*v)M?3+c)X}W;f9e5A z0ma)>!^hTpyat>YI+ePmnMz+dd`zz*b4C2>Z`bxdjyYapM5u*^7@JWHcR(ZS1iCfO zi`Th|n=_9QAT#`x*FIoBYDP`3YgIGM;%`npNRAj=%yEUzi5oopI6WnxoN}{J$0{bm zw5)=6UDGa1Yy-fxZJ>%_t|>CN^%O7YQbO8lR9 zHHso}ERB&;)P6Rb2(7i=xiBX(86_~36Xhw`YCHuXj7v*L`XirTg40N~G1OHoNK1Q~ zGS&2*ub?9ahQdnuFz4-{l4Scn>c3!CPB;6hL;d&XqEK^M9pwu3L2K3apv}4=7vKjl z&3s5H>sXIJICWbOt5)_&9RzMZNFBs}(di+5WE0v#er*xbLcJco3;Mo}?_5KdIPlW$ z+%|4%Uj~ASK>O4^;g_{QcjnyS{!~2I`U*M*(B7-W``OuY1;E|B1eUk__8Tm3>CHne zmo1=xbgA|&W!rx(cF=2UpPcs^Kk80`i2Syah@9s6)*8eFOyTQ+J5{gR2bp#Sob+Bz z0oEX*QsUmq_tAC|fy=kLUg9`B)8H){YnY(-YGwI)+1&l1`HY92_}3*@gD+p?g&s8m;;fNMRb`@>ba8!JT0x!!^(Y*6(7=n3Z1Da2lVeG~pA#>=I&F}8`U^TS zwfASVA4om^RJSzR(NO7Uuhtor^yv=E{AM4R&J7SO2DHMnB$+E<1`X{J(;Xfqzck=7 zw@A#oqs~(r$@e|gV3{`S2dLV&gA_NZ4{MvJxf}A;bCWdVnFvy)93@u;7#3h7 zhUNkdalt^Lc6AF>%V>Am)Pmm@38YQZtio0E+lxA>n6XNz=ct?km9$Yhenvx;Rgq4p zgVo86c)HMex!?t~;mEWBOF58jJkiPqc50k?X-wGF(LUG548}aGgNeNd75WU!@$R}) z{`nbrQHCvH^JT7{NzMIIX!7UTjsZ~2a^=jGc7oQ((HPPbPy?wwhL7?3H7AsOBUa2E z;xdvF$`0$g`U1n+Beru)#Frj;;Sl#~pah`N%?I~-8u04_wIP1x*1{k(L2_jID(DE; zlx^$UI922jL&BuOWxn1Ff;Jx1shy;1ZUhJDV}-pPt^={&~*dMBpJ4IoZB_! zp!TROf6_pObs45O;VoGkk*}H4?&jqP@z8@FadXB;7VhV%EwqNh8#ttG!gY zvs;-VKDfeRJj^~0=A=){pg86*FY09j*E32SbY1wp8kpj9w4`Dtt;Dj-j6uwSAsWoi zW|Fn>e7t7Dc{~s8>I}f*3^bjm!Jc`vgKc}`Sx!pEUKpcO5VKG?FBp+^8Q)J)r2z3< zjQIp{`TOp+A+|{1NufM<&h{7?E4T4#eAt6S>i*kcdW%D0y5o{vtBV25Feu4x^ey}z}kVPmM(=&G11K0 zk)0BHaQ}IAl^Y`!T|>tSsW63#-kgO@Gz`fIhHNC=yr`Kya-E%Hc^}A9j4V4CaGjh* zcf^rxPuN#Uhe*j}cFt#3B3Xb;uLd^J2r9<;-mG(%EdV4DMXX z3lEsX2cp{&F0xvi50lARblyDE;(-H8`I04?8)=1JCm7n|>(XyZny1r1nGU^HHElM! z-z3Y>xKI_<_V14f%9l@3Z#?hvV~aC?9>khBWaRdAYtCJxkKU*!9~@#^RX$h=*7_9T za8947DIpZ-5QndwM(G@wf>{=d={3sA$Ya)K4ntfIhwK1Zdf=GMpICiPH3HP}PdtfK zmsB%k(Ft}swX`m^iYu^kfY1r0I+g%+T-oV9do)_F|9t@y$BBU;Uvg9DLI7T3C?+GhQ!O=-+0Ko z_Ht2FJ8G1z&6Y5;iRFk%|J3@`AY1z&S_TS^0U@Nuf(tF$ZqS>f!E#wrIe) zeSHghh4YEyCP3t&ahEr;%S0BL<+n-$XT|F>TYJ@@P=Rqcv>|`B z(v(kXlap%~(?0mJKNLiJ5cBm5Cm}~{HZ#uQhV^RW6PIP-X~n*AX7uH{>5jAVHg)gd z2FFE)Je$nPa*KXhj`}x*#;LN@hW5~y#vP(EpZCGYg~jNa-aQRZJi1vh_h8;3G3??( z=QQpIh|`ZEN;Tw+k~WKwy?l-cNtX!G`(#4RBIE_d;XIUaVn_;-#hRq~c1a8$RfpAscVuW=A8>CxpovuQc*|$cGEt zTRJFJL#IdQvG?0cNxdE1$(aLw;8CAL0_q*PS3%s0O|*#ZK$?bm1BeMf85@$)=^X0u zy{smS$%3pT5spv_)=U3XKCRq6pU4qOxu#SJ=Q}OfU|u#1j{DCV9$)R>z<)dFK;c$g!bboA_$K=2^3z{y zFLeJw?ZxT8iZ3cQN|CB93?pf8iXIw-ccvf`O){rIR5i4b z{==)jr_x^{egeJgtK-ty61=V@d_{bPPn#&o!c0abc&sJB7)9I|^rM9A#skBxx*X6}suhC*g|^Aq3~5y2}}mfh;I;63}19<*G0><1j+yc52N#9op;W zix2wcs%ilS?fFr$hld@KzCm-NK9Hx<1Q>Es3=$Eh7}QXT5&g;I!u$1Y$TUNYk-^2o z#DO-FODaYo)LlK?!;y>Q$WMZbOUcfSn#yM!?PV0)8=4Z!lla7~$*M*&HuO9j^bLR#!K?& zZRo?4NUDDq*NDa&>?lv#D=m#nX5V11X*TLA8d8egcIkcSTpb1B5y@_j(2&_WHUyjl z^|RL<{PEiPQgZ-3Y%{u-^mw1wBaS>L8;;*??N1mZ2iT7Gq`5 zmQVxJow_(nwWN-!CW%zpK6c#3ZCIeemU010i}{Aue)jAtVtmh+D9T zn=Z^1H=rUl*`}x2ECt1^jt!ZpfpkUaL$vEQj4t;5L0YfyxeMzPeZ+-+6Tch^7y9Ng z{~lnQB++YQ)69`-ZN37bTSzgEeV_dcb=8hk_-EOaa)(1|+5PNK>1%{mrIWdq`@Gj+ z{OvreQ1$HRTBQS`koigk7cbud zziCK)hj(}{CTQ3YDoz`#{yWB?S*pIwes8^U(3Q3w*1B zY8O8nqKi~C3a3-06<jhvDVEFU|zUwpbR+S=2dlE!3y zfPpNOw#4?Bv~FD1ca-Riq0(Lr45%!EJ(D~i(`avrti%`OVokNs*Fj;?4PRR9Mh0zP z_cVkGsOiJ$&A(Fep`BP0`iYo4sZ(Qz=LIL8W0yFyKgKx+(wYDv1Q94`FnF3B=My<- zYo;msM%7N8XE7s`eEl7yz+wZ#~08|v1=bf*S@-N_w@SY;VvN43+niAJy;B$v++dI6yL{Gl^s)&Upmhl6ttFu!c ztb^!fs;O2>2YOr?XI{sHgf|3n+VA#kgB3W=rA(xXtC1c@!QzjEFlgm08)rn`Y5ivf znJ&rI@qq+gXFpA&blX5`>$K3l|9)KH*oT%``v;BJ|6Kpt6*T`Ium9l+B_nea8-ss% zLMDpgk0+3Yk4MwW@X6i5njQL=d6V@K=x%g;4GmeTGF2WdQE~f(!Xdyphm9iG?mhW( z0G825!i=E7_qDw%Vh!E>_@E40)NQ4D8Yt^H|GBBU`E8&$j;cp*6bhVCM)YGA*rGJvUX0X&0*9wARik0NO|9YMuO(r{D?6~*!8mg z>Z2dlcW_EI$S{b_6oKKBlz(z%LnSGKlI>3Mv1QY;-GA0(@iOM>Wj*&@E@#`kK-KWr zF30oqHIV=+Oj_Ig((Exq1#^3tVr@9F$q zgVPqR^PVReMhfnhBjp~)sWs%`X@-0jqeHU}6Uzkl5JC5br1Rum+blR7;%$6St-K|T zM$!HlDOTJd=M(ejqI}BAWa@#kZQG26j0*=5MSt$UY9;OA~^iLu@qSvqR^bjj2teiYRGw6)sqcBSz+WRJA3Sp82kccB#zrHV<;dl z0Zl;uRc5N40&E=hDHXTb4_aB|99Ji42_~dUu`V!AX@L`^VTa#+Wi!@>egq%wleW$) z7)*K_G@?vOjM(Ih0vD|N4*oZ$xxMv{X#b~&3Los>As6$%N3MT5VPJd+qXg$=^a6%Mmc!!v zmZuV(r$PBAIu2L&lidqhS3{0lV<2EVe7pOM*R&UJ&V5$m;d8IA2gD8{FAV>XAiM`M zaaM92`Vf2)K8D@RK9O(PX3G#xq&Cef!{p)U=3O|R0_}uvlzsUt!oH)%0HlyQ1?};K zNO#`B5@>gt&bSq{Vn1^R&8Tr4$_8t8mI*VaY2a>wX7zOmTeRqc^VOB+sZC+WLfOWt zoe5eC6e1HOOFZ4Z35eLSEmJR9_*A*GFHTBkCgzX+unb1%U zE*ot|MKpt3l2$UMrgkQXWVOhu=PHAmELoUzJ1Ph^Mk@z)-xd666)qEul~cj2MvDdT za^yo828b}D7(@o?tPrBhHJCcynJM)mG2Oy;E@opdn>0d|;@ne<7n)8fyNe8$qHYA<*oN3$K%9raEz9Fw20a(%JD{0jVQNhT z!YxJ-*h%+#d$^jo>2;7@%dm3iFkaaDf1k4lJ4j7N-s@R}+; zf=3LW;|;lgRE+a`1e8IVb?e)U_VeTE&sOOJt9fSjm=E#PE|Rzt*Sj+V!NZYwwgHRh zrNbG7uJPy>xjQO;6H5?YLW9E(ha-UemS2*+*8!{e0#^RCy`Qwn;k?Y==Qo85VLqMG zK%{V+`CT~MbN!RlgX8lhPTewe+btAi^9{w2gc@qk>48@uxAHO4hUUQ1?qywbb?HaP z#(LfZR`~$$TT?_^Y@F)tB~g`1$1_7251^NKUD2hB{G3>B3_7dDjONh81K_sCWG7?j zg7*}KbM4PSTfTl@p?jX5-13^f=Dun_&cS#a^27560Z^6DWlP#4 zVAGD=x9tLW6 ziqD3(cQnZ88?@$~lxdZ=GHTqVIuhGyKpS(sL)P|PgvA$}JKcZoKKuA_z{K&Qam9BW z!r0}Hx1AQ6JDR!a&a{;fsJBDb+OaFx#e!xxxbgZFxw*L;BDa_A?SjVU*17BQ`6RWu zxi5-woph_qwzv=D;f{X2KPqO-eHP;4&bN)dZyJy(VH@Lg{}T&uh@!_c43PT*pbhcm z2>}DoMJO`nCJ3u$gzo-l?aigprnkySZF@cr-f?E)rgwIxo&7xs+xEec7We%|`wPwo z?Y3`h=C#v3u&wW}so1vH`Y1k!!B@`NT*v#OD7kt}-;t>q6YmU|S17iZo7ByZ2)u92 zk?l4EbiawI>$cbKsF^l`pEHj4T(&1;c$@SQGh;7Znf4!ZFa{q;k)LL^Uwb3Hd)sZV zUbGZ1c$06r)?0qDuW_yZC?c72Q^6>u_PZ zJ-RVpnV)Vi|6cQ#-|X4;n$<$P`4M{7QE1?2Zw@^0F-21y&2!oLriGR1=HmMJ*xJH; zb#`ibYG-4&IWHr<4nJNxdSQKWW_#KItHJ(xpOO-jQX^Vmh+_bMa(1z~w7s#qwJ^W^ zTX4~xUQnl#j#-iIBwBdsCO@A4w{Pt=KQVIEeei&#PAh5aH{x~APs=>jx4{a#O*HvI zoiqFa>_p?vkiiPKJf4I5ALjbDt$yU>%$(MI75xwFRj|u0p+5?RRbVOwa@XS^*VPyC z%B(Q4u#M3- z8`Xys4_JJ@j}%tOQeLqIHJ|=sottzT?E*%iQou1v6ktb+;^u@9eKvJTirR~=00uYF zG3qb#6TW;D`!H@D(Wgg#6diL2AG1#EF>+!q40*a#^O-xE{dp!-CJ9=?#Hxs7GJ75w zGO%y>2%nn%Sy&{?g6*Za$@izocQ@a*I#}R~SDsB;xtkI;!!q>UzA$m9!zkY2o|90~fHl7hdKsjwgKL5z)% zy&~n2kX^$jsQovdGS`UcQ~9n=R~tM3jpxbZd_bYs?JNe)li0kv5tdH#87e;f5{6dH zoH#*)i`&RSBf(`@P3iY=8YYg|jWW%IHnJ<+l3N~)d)$Y2o(7S;qKTtE4e$Bn zqEI0-!7iKY3wv`+tZZe=5NBKeU_VhWmt>f$yV!6Y2L!YS_Wk5O{&KOXpnsrr3~?mP zVsd{!QI#(95t-dK25fZ2?uomZJO3ej#p@t$!S(%*R?k3Sz~(W$hmcV&^I}|OU}8&x z&v_(;wOy=1gKPHJehVSXCKhDeTg9jc*s$Os#`yc;+uRgirM-oaGiB7-a;R}lW*R2< zE74_}kzF7mWi z?7Q+p&At?pU}>&Pp(&$yb8}(g6K}+DrAUkVgFsK$fI8wwLL?1nt6?-q*tgK8PY=5i zfjq}`jz1m3Ne=Wf#;r#DbAb(&lxkkGeu0zG9zqM zhGLYDl8uE)!Hcjgf+#wSgt5mXBiacNYjV%Ffrx|yub4Sjl&(4E%v3n3LlpNv?| z!ix$R*CcO3PY+J3%5O1C$sU#&96z%N0(~0HDw3MP>y=_0~1E+-G6EfYQW>{xH) zKSqZFgN|BQKwwo}W1Psk>OcpS6Q8P5uH>cc8tR9N32-lqa@ zW^S*hxj$tR3=BL-FUtEV*|BX;S=onA`o5abPhQ$IXU~P?e1U~!>^zmBJ(a#dD3H;J zP$d7nzskg^bru^%9%YYjO2ZQb?p3-!k65&?CE7usRzEYRv0lckuHi@w^EOW%%f59$ zkCure$`wMNTSBa80@{*4F3Dbb$}6?$PQ^?qHQaPKd|}OK?d#`_j@6*If)QFU`Dv>s z2;dHtcCm~qG!nF6ruxIARd-y$kdO>K!SiAY;ewo2$nm(mrf(_Dxyo={^~@ zU<^wEtcuu}*39Mp>kCUGL&VIAKxU?9xpB+bp2MYmTAB)Bu#pJ?E?&G!3e&QSdGW7jE*6gkY6}wUSO|Dul&7GUbr`l`WS3&kN%JYt+w=oBPjbypjdk+Ff7p&R;vgh^OxNN=p3!Lptj@%tT%pkM}?7EqwGIJVg-61qYlam*&t5*eefzMm<_ z&7KlZ{*biQYBXAU`QnH3-Wr;D;!wL9L0u7)3W!CyJ9am2U)=%7y;X+zMWU1e`Q{>F zqCua)6?ygf19;WwPok6s1R|OsGl+_(&Y(H|W+tc5A{7U`a`C8AQor!Ey_I)rZ?dRq zREj7q(t+p(sUW)~qYvUMPE%quZYbI43Nvdr!6%g7*B_w7rgX&B*WHoJL*n!S#7dG0 z5@-^0>1Q=TXGWortp{x&b%P9gE{``Z8=AD^&x%mI7xKlaFg9j(B-ZCj#!e?6=u3Bi z9?0h@Po+gPX_oGbTzy-%hPKGZBPoV_VPZ9YO(EK*I4Dg_N!qGX&1;@gYiO2=pV!Cd z>y+u!U8&l`T^(-tK)UY9f@0lR2Q5q*W&o3XZw<&$U0$BD3SM3ow1}<(XPPsWE`+-| zE01;x#A6cgspQ5)(H6uLoQy>i+9XXIFlM${9F#!pRw+%fy8AW;fX>%%&Yo9G}S*_?}uoYXTRfY#!88w&}#X z4#>eD6^9*`e{PeeF}*vXluL1<8CZlEXbRxL-VE7l76%>(G&D)Ydy1o$r)K`%%p!ZF zn8&rKCCsK%nV1Jf-YIG&NLueHE`70#egaQE-DbFfh1W7Fv4c=N?=Q)~4dvIXb|z(X zjglqOA6MkVDQKh=nwSr6vNF?Jn2d|0NJw36*R*5|-`2yg;u)61FdtoP1f*SX7$Z9D z$vr9zQ&u2)4TaNYlbm2VWkWgzk$dtH7HuebX%nPkxT{o>^ket_0oEpMq((2;WZJN>xzN+gQ~sF|If_9v)KSlfMWnYIi1Y;G193%fD3nsMf)>7&+4Q!68|WQ^8Y? zR)d=EHjgCv>tl>Z1YG%TY2=UJUnG<5Y6d~)~tW+sD_OZRdF4Y zoXsJW94~A7;8>aD9$}pt7|^g&1Lq+Iv>*wzN|wwYy@RPrv~ZtmEV8-6rDMjZ`Vnc_ zTa-)hEYV5(;M&{NFKr{_Mn1tpk~21UQh_^cuu%qk#bZv}wP4d80Syw~AA?PG!)1(G@k)5Z`ugY*cfmFe&Z z>@||~LTn@M3pxH)qHnuRJV`~H`}6g~8whn)s#SjP;C0BL@$$o@bnb$K{G+_<3 z4to`hqb>BH2R~e(7iyb`eVa}HHW6=j3GQ?quuYdb8xLU)FHzAYe+4rCp2dRaSx6zP zuR{%w9wO7u{ZVdLLZB$b^Gn8Y(ZO?<4_T@@>!Qb9Y-(R#QRx$aS^!c;sup<7gNvOd znqAt~4N?2WB~dRGtA#FfyU#AX0qUsln!G|XW6EOLie4>b+s?3Qrmr#JnH8bmv;iui za{C8UL?x_M0lW0o6LH%u#jnxaOyLX)b;#85(onyj$3d#Dk*liKvr~`Ca(xZL^E2?L zX8G{BOdgK)1TqZVaDGv*HUu9MTHxXW0K0S;ZPQe&qV{)G;DF9Jp99o7OQZcp^?AhU z^zG2nwEknzhkp_+ph^4BJ#L^SRnJ#adRc+mFvB&z;Z3L@GUQY}FpsL*j17uuMLn#A z3|Whuu_sfTPbl5lGPJ=+WIUIR34Dr7lNYwn8^$g|b({S11k)GZi1{JI*JSJpnq3)A z;JaeUL{wAEE>lR|Uv_pNbF2s_5zqzmDybXs)&A*<)#29h0xjZkG<%rs_+B-;(gA0O z;S+*t6KBM-MGGJKrrhQBNS2!4c8t|45Y@GHmfaNP%iH4RF0Mnu)l@@P-up>#==~F5)x?~&qj))t-DGk1vs-N)W zO#LQhZ2VfYKcT{g&!{*E8uof4F8o{=->teadqtV1zam=X)Gzg{o4+4hp6L40U7zS=;U7pFzNaKE^u%9zh$sp+84x7G zQO2@c#7zytaS{?hh@TM>h$MJ!4PYcFrcsAPgGkK0D&*#WZV=c(p1oI)8NeJbGT1Fz z@IjKyLm*wQ{kU9X7vqwvKbzpUBx3Up#7Xd!1bo1iBU&~QC7h*Al9!YuUreX+eg&u! zsjCT0BrQ(Lm(G3zjC5dhk%e9ki`3goE zk_%DP4k}%k6qLV#=6vI5deHyD^U6fKEKiL<{sAPC&fgz@bb$PhThPxC6{8Ca6Hv+--)@RKN@(cYL2)s~h(0r)MsFlS-_mej zWZWA6HC^9ON#<&mP**vUk#u1R+Ic?qQdNku09gWa?FdRc$_aDG=%26x zddXbdkC-l7t~yBtdQ=#c?k?CJUmfVh8s-kg3>2%gNaeS`= zbc6fL;UXkG=3c+25Kh+Z!rwB~SAJ8xcP11FhW$GiZV{{R9Mrx?6O_$Dsy-)Xo(I`DSn725k5Q#vYi(p-&i1+m4iEqhIR#tx9KAR@Zk z75|oLJYpR3XgrkJP#)KW{0fJlfUnLqmvaplvImC@KX)ZV zH^h`d?yk#Sr%Q3u6&aY3Kf}z!z*DE&pB5TJu(#~;xaqWIeEL*Fru$r&o-b2}pW#xc zo3Ah|X0UVFRg0$U9?eHHQ4*HVKwFR@cb9JRFvVmn84s}M8s1y}sc%PV)DhOQk|ZkZ z7x(uh8fX!t<^k!nLQ>dI>Gq)CSaSyTyemG{RnhGZ;(h7BK|g?VwNMf$`xsgOdEt;i ze#o7nXxxj2W`REI%n%YbZ(ZrUo*A6PHETcWBAU4u8V4pGeoBI+CdAKf)WS8(kc-f= z0^17Af!j36gdYR-0(R48eN{`=BngS6&`N$Pzo$WJ%6_{#e@o`#lbn}b)_AwdWL_7% zh58)()|*bvS-=qj%gx*Nd8Tm|t5p)oOuB#S-9_xPy8bZcb*)Fxg#7B%GSMu-*_U|z zER^H|bjGQsd59N^bN-{&=`+qR5eqj_NA#qa)+k-qa*_44kY&lb>Fe)QoNdW4C+I5g zjc;wbYuvV^&a2bu3@c}`H<^utO84xa@;M6`VuhImo{r_jQ?M_+-lE0j(Bj-fJT<*l ziA{^2Fc&PL-O)oU($QkEVqMeaPn&}Jn$1$18FiNXYpcO=%-?&u6+><)%#ZCPMxVj^ z0XyW|e39_nu`=@m)cg!L6JG^YL zwb@5+)TwXCsc%!tLmK{!`_L2qNyp7sOqLmTi_+q7OLwpDcUW1y6jp1q|5B^xtk(Eb zsTPF6WlTha`=Cs#=TVRwlp&OrA(6*~E_`T;VeU|73X1)(&Pp^)@sb>=LBhG`2BF$TV-#k?RO?31CS+hCXh5k>Zg9Cy7hdMc;BK`eitIj8XPiTInET?2 z?a>E8&ipzWwGz{VItD!&#bS(1X-O$hrIhQ9VnH9}f)Uk{Ke{D&#G1&7n-c93AIEu4 zj$W}Kp3B#%5$Vz-seo~zBl?XN`?{9Qiiw{e>_{jPJ4q%=-_>lxGFgv7qKy+0DN)C6w)oJXscc~1XPlAWH^1K zwJp+`Y4C+Gt|&QoF^agW6SF9>FspPV1Bs}SFe^sHWR2b2V`TYl2E$g{h! zD>iX=E|Q*5WPLM{b-U1NE&}pX##?dci$;>4-5XxeBXs8pw`KqsLtbDV+5= z2iMyvGaM~@aH4MWs-W5KoW12eqYQUnY<2XN(qaLLF#*5<7C!A_G zzr^(`gmwqVu=s}D;zc+1wywbsQyO5zYEH>YS}PgsVI#TZTj6f@#L<>ZkJ(Spfy5^` z$3BG9P|tvxSjhcCUM)s$0wB+{jLz8%`EN{a@!v~jQ~E!^HQu}^4)fMdQyv&>3I}#t z-_Wt583u{MSv|oytI-ehel>W7=G|ks?<@YYZ@%+s&8Tj3ebM({o4&H`COBL9{l#q! zi>Wa9B>>CPHcZ5q6^e*g{vEP@;Q@!XA#wtZDAIMUqOa=r$aiMuDazgM(}B_Vj`pbQL<4tfn2IT7gCJPq){8N%FWzty6#-QA2Kv0v3n%1lv-ZEDgNc=%Y~8 z+-7KX-o>oD?M{@pvs5JPS zwRkhn1&wx}!UL&!RvpHueq(89mK*x#mLD-)Cl2RK3mvJA($v?aEqdu1TjjW>-jn=i z^exd(Knp&HUU*uvUNY1T@pz~~BtPA~C;7_joGomi?fMGd1Zm4z&$;rjUo%p!p7{Rx zNMZ(77Y6<+ea`+rrO$ul3EBV0ABltWe}GX`WgJj7(7q`MEXK`+WraZypcjDr6R|)p zLqb{O`Jh4l!4mRT7uWMRQw~j;%i~j)<<``H1E+Nx?}%+RQkCX!9L&dL!(bnzQP_BGs;L6rCUH z*XFmM(datN(=#<{JnHr9vzy>Ja#<(#t2J%@9HWted^yazi!E8oBso(x*wZU~Su_{t zGAmAc8Y$$?%Rjl;^2>`E(3`#5SgWe4OsP>7YPtgrT^K}wnZsJMS5RV6t>f4K)@NAt zZJv~M&n30ZqdV9#C|avZvlo{i(Hi6{j7|CH*sOga_ z@}$@rD4w*_e|cD|WZBBoqcdZ&Y-wd%($3|;j%TZFT#M(OpfJEbq^rlSnc=9gRs~Sx z(>T6VH1H<4)?J6UjSnM63@@My%Br4M)xWBav9s9Fx|q{D%xbZcawWCEf>4N5k(=yA zYK8Y{5=j-BES#LF($7+YEbb5*IXfk=-3UF0cJFhj*I`eI8B`oRfoc-87efV2EHmF-P zH?tY>^)_2_zwgF!#se*%b2_Koce-|-E*}5f1)noFh^r_SYsq)-rcX$x?|^tJ|1l*O zR{6R;ebFMrEspc?a;05oE3d9O8&B^ak}Eib{hbI$>j}#S?K+&YkS#H!wTj*wh>32R zsFpa=8T?aI#Sx_ft(FCU*eZVo4R44Ed}1qfGjvOIQ*}D554}#m};!l3y!B{!ayoI%C&rWX}kn~dD9v`4xYHizhjJ@_zDNA%l*>F5)&WGriM37Y8%XkP-!OJpV;zQi=G9CzL*b}>TZ z$6a`**uMTS@WLc$Yk4u^g4g(`x4_W1<-wvB6V-l*D=3e*C(!`MQ;|gH$4NrIPV3My z*<}&yAP3rsjw^ua^8r78nKSx6Z`6Xy6B8sd1%~8v>)<@rAJ9GX5lIFvf58u44INc){T`E=UCGDdO}Kk9Wqf^pdc^E(o&D*}JxUbw5T zNF?I;FBrU>93pAb-8O>Mo##nB{AzHNNNjTP@}FbNdyfE`ZEa{ zv*FYV*e}1FVbhCA4h~qMil2iG2EugitzM&Fn_iH&2(XcI?K4LB`xMu%n|JFJ*Up+9 ztEN79?HaF~bQg{Jf;K64E|w00*30}6cZi+yka*rd4sDi2fd6c;0;~%47^C5DmSgrI zxL=HIK=r(ptPKmb`aDEWf)lSzWL zk_XAOHl*Sa6~`wk0V=%-4G+kC*#Y%JLG{?jbbGqq8EA5&=+6m|e<1#uxi|cqkuC*-r@v=}p8(N2!(HY`j#2E5pruJ&LX z+OG9^5p}0qy7IiaHUY<5y!yPEF)!U;NfUVsZLqpv-<0+8s>3s6 zy}HA+yNU?`=?~K1`s9GMUwz`kHXlBDp*HV6dccr?y;{RMyN44xo_~to0yC*5I z{k5n>Ef>-)h$Ht}ISpA02Zp(zg=!R2Bw|s2XG>HICZm$?_F`0tcj%%iQ7W1T_X?bl zw0^y4agZaFk?8pfPODQbA9<A6Y_VhZY5q@bX2R9ar*3r2$-9vU9jr>vRwM}NCo$NuOP0f*0qJA#kXx?HijEjD%yQC5}{#v(>fK4S!i&zN1n=~r=pZS7-MeaO|M&t>%5M|yYkh~6S{p8#eh7e_loF&&c<6hP%f z3hfRFGI?oTx^XCDd$MQjrL^m?MlXGu5%XUKNfZjd>=ejjjP?gQhQ;)-5m3fYqkJ*h zt#kOfIGO?SZbi=dlC7U4L7BymH7_;5S;wx;CH;MBM$Q9dMI=a1;@cKpCPAmtjd}9W zSDAY#k!xV7g$1*$9^eVZRM=5+QKG%9!+L$o6|LhJH*wPMP?xdBJe2fHgEZPsjBdVe zlMM;;GIJZDBu2?1%g{7ObPh3Uc2X|ZhHuwZ@XMumdLR^w$ojpF&Fbi#f@KSj^_e;b zEJLe$MQ%2*9B*7Hs8o_>$ci~0WN7zBYkaQ3)$`_mRU*%B>Hp)7)o9Q+@cxjTRnVIpG`Y=Q##_=aIIcz#jJZc zigh#_sp_j>eSA@CoAo7aO__Y#7k9lpM~oE4uC6|^Wi`2h^%0ANnsM4|)f9WP^dq=- zuaH}5traUC0?j_sg65!VMm#4dCK0hUL3Up)g+#9PoFMJm2V4(Yo@=Jp=#eV`*U%^EQ_5o zWs2I$%zDusnf$_j)g7_A*}bYq`rSzCqHAf2?bjZokA^dOw#_B&)LChE%(EI~4YD+T zO*rKf!b*G2rI)&Q29FiYOZ@snvZHeWXY)8(fbv(rqfL*$p(CZ>N$)3qQfvqA2AJJ<<0_u~Uiu<{r#WXbR32tK zuEf(Qc}aV=o|q2N#W@m>yZUB|zslo#+PI+PqYusqvVw{-oI>XkYKh<5WG66T%A?aR z6#$i)!Z9T_r-}&rEs?7ln|;g(+OXY1OW1#9ot~` z1$9V#@nAfa%^1i;!&F8k5>Zvha^Sw)D7o#dNN*`Z&OYG$#n;bZ3H1#IYc>K;oka zXU^SccwDAOL9L^c4+d!|mSNikt1@%YBnMYSI`!l`axJliyxS~PM7HT}f$vGJtOGgH4Yrh4X8+)VX5;T92*o7=Qt<-7Ioe_~Iiq^BKkt&A z0eQz5h*0xbVt>nNX=Hj}gKzXFle$}?23zqp^tLtPleOIR5(_Z)K%m~aq?paYyC?y_DyHByVxx9V}gpxCs1Fcq+HHeZzlQneR zuk0(zkU^!Bl)XNlayvI>&U4BZa?BNat!hW3tP0B$QmkS! zrYj9+;xyN>VJYzf%{d)<(${euOm=`0A6KSTjN*f7aksa2)U|x%=MCz-&C?#$&oF2s z&klFCTQ}i@wxfC#Qu^y<0`r%t?7N>ieM{cWY{z2~2C->pR0M5}$|{T=bL$P6Q|?dk zRF`&j6ShIe-5AU|FxCxuY6_T)#_Be``6sHY@_M4rS5<2+RC9&>s+DUIcy)JOMLIjOeRZsy7euXvEUq@pEO$YT*Na9wq=Gg#HA8ynIo1 zD*o07!>loo&Re@L`xWNf3CFM@)H}K+e=sa$Bg8;07ig!Cr>onvK&z#I?~cAIHv$w> zgcz?J#J5cbuUV%D9H1UX9ZCzJ!SQ2T!#BJpN4jBitbiH~N&GSaK4{htlnu#nB zJoqMuc9bRHopk@^0)@=({=Xa4SpGk?-0y{o1072wIY&1$usTFbT* zi5P@8r)hHe8lsGvLm7uito;_$Jb*X(;I83kA2iXke(I@ZA&iLAoBk~W=`0}hwP2ZJ z*fMXD+>a~Yo-en!V2sS3@po%=coItgP&#cz%e%J5Z7b4Je|#ysw?)_8A~(tHk`v^W zT^|I}{PN8r?GYU6lt?9yZyu%Gu8m~7B4E*Yc)nGpF zs%f#G#Fdi!!S-V4i!T_r7XV(~EA)Hz+EdR5#BK{vRifMyaXx6|vtKGYq*x5(P`z*W zgFLfi*(mbK9pW?(ZrXk`MaXMIcq@m=oUu1UsA=At@Y}rT7hgy-W5`ebccKvEv292~ zZM5!$MYb^Yt0t0)(D~#b-*mX<$~AwM&fAX6)ohbWHb{PDk#KC=fbhsK7SY`T29h6T zCP5H7;MWv;ywjD6)#qexFejo+a z?Kz+YRT7*V{2DC@uAL#=y68iXR9ayz8TK>5Z@?#;0<84%><^~+jCpUE-1k=A{zvQS z$v_EXJ2wZ}#u+<@R}}RI)isVI8do&SX>V=dkGSs<@t(Q;D#lZTU}#!T>Jx@5IHI6( zrIH^CGZ20v`*vl%IYJ*A5T`wG({I?Rgv?ax*S|P^Sr+|DMGp8DkqB=;n=0kgPx7IK z%QJ91p};cd(#7w{UbkC+TDXEXI{TDC?5w|L-w?rf#Lo}APQP;^&->s#XvM(qa>st1d^NGfUtTckAVndLa%-wzS|%H*l&0FwG!8 zK1f^>$QtURE{OF7j3gh^a?q!{?Mj4>6ApAmlx=s{4!hna)0;ExTiR$}UT4D<`%j!mR%V|@t;L*u5kAoe7arr!x^TyEccfKk{1lSryZJ58TQBZuzKooQ) zTEyS{Dlhmgr><+*kpcM(bFv4v_$xG7<1q~qIB)$p849ft}1JpbXJn>(l9fp7bcJIQTkp4*S zBg96C(!=_*==&2OMue}J{DUK5367DCgb^@=$4~%NJaXS~3Fv}jP5^8!k*^uA0UyjM zCsf2D4FVP$b7Uznpy{H>uA+u4ex%5=)IW_bWLPAT7SL#5m`${t;1l?T^qk`8`MqFR zEtPyBFT?-wCo%hxxWWc6S;K;+&lXVFiCUi!lFJbJnNa;EQJ^N2 zn6UsB@oR+gqhlroLNNA`p(Rp6Ad4s>9Y%~GazbbzCtUH7Bc}`=^m+7{W4|T{W{g>R zsb3qQuqNd*4A%AcYZw+eZ*FT#`ByWy)=I?16Vqfq<8s4D?)$?4;3GS_q}D@(u85ZL zEma#u67rA1cITkAR8~w%^YU!2tnOc+pFzYHGhRN1R-}mW&jfZW?kkp`4PFiaO(qw` zq@1T+Np4nN;?m7M_$=dN3Yz~Y(3$`l;b#Zlu? zBNVBJH7OZXB-3SeOC~hDqlukW9ed8I({&5D22YIf z$dr;|V*gJY2P=(tg57m6@SVbGrm7;;SRp|2C$ zAE{%I_)#i#wC)0}fL(qcfK;LBfriVLWyic8SEQT9h;!osO0UvJe$kBOV zLED6Zfsu_j9XV)1KH}7b{>5z(a@q)|p34aYX+lcibQJh+rB9Yq^5-NP5K!jd2G)OG zBmHj_5&zUkWgSfZ)d2l>i8M}4$3;aA?OWcSQcB^H4Olx2O>DUosmm(}hCa+#CL5%z z4!kJ0yh2VoTO}D)9G{Ui9wCAM0#<={N+vI9DIGh@=~*c2QQ}<&>qX}aZ&@A}Z=uzv zuI)|V#_ziQ=lOxaACN;R_256A253&Y@x{2P$j%z3S*d2y=~i0%D@>?jXc{A(6wkkZ z)W;0R{V*5n#}21 z!O4!eab9DZnNQWK))MM0C?CqeU0uwX+nLD%Z@JP;Lrj;+B4-JxvCd%e>M=GGpEA4c zy>wXV}M7Wg(3HASEbYGg7J&DjGR5YRkrJ*b9QaF-k|ZiZf+|q z;_ETpXJR35>CMD0&}n``xDEe|IW2aP9;rWhJzh#m+NXvOBKFx|UQ9-3Y&+$uaURR1 z)iO>3l2M@$skpI&$Q4d16ke0o?~1bz$MKG5l}r@&;BdbFh_eMD+{5$tBo#JUa;9Lf zccbvZ6~hM9)t+w^+?b_jce1EOGy*-hw)6DLm>0RUnG)|hFMN4DkB&x%jw;h^u649lY?$u|Jk7L^;=I+4$w6K0)xm9 zEH^)bHAU|+P<`fJoJfocL(N`~Oz(v4eYq_Rkt`xFO@(Rh2r{6n7y;|^drk1U5(D9& ze_W8@VhvXZf#g}nbice2SbB<;{|XG3vjk0kV6DAX*%X4BAXW^?gn?HP|> zr|nno$*=yLKQ6{3ERA56;P!&!>v>}POx~aR9EyPSkfS`+DT9l>`J}cfvQ}MeDAO!c zT$L|PybzUng(8#eB}sb-%Z!3F9h$roQtw1`ic1ac%=P9Gd%!(m+zwt(S2#vksj2KD zf4)%PQSm{bdBo}}dhy5=h$3Y9vj{i9P$&V|7b-wZ*r|v~AkC77F)Io4as^v9&+t$#uqicXDse-Zx|G=?cAP zhnL~LJ9=e)8{dCQ-MU`!fPvOrRzQ7ftPBwQ8!!!!@ShE{eM)`{&JNHx7Yz5Q=%HMs zjvywYyql6DHfjhH)daW@;#axdTpbH|MR6YcaRS%6`%n#~Uwkoq$L4m6ZNnot%vmUo^JyY5H9Cpm6Y?$h z;~=E`{_yq9^%vbBT?@edjeYK|^m0=c@ULqL%7ASYasPIv*8a=R6w80l6et=wyO{k) z=0tf$8dM0WmOTNU=@B{3LCgUs4pu)+w^&#hiJ0kaihF!*E`HW__27xj^8xf1Rg(p3 zFqY6**zH|bRsEIy=k4!P^g&?uW=Z3*Y&A$5gmq{ZaccuGyQ;W`;h1fvzm$ouS{Mb2 z(JF?JLHUpO$VC1p5rJwXnw>aYC&pdDP`wUpD}fsVcHT9YhR~=|{~PeTxCi?<7(E56 z%XKbmDg{wGm(CRmvYIPplyY2us)f(K_*MG9TitR)BOx6}JzoNSiV~Xxq}>9t`7rjU zlckiu8g}9(7QGoRbvgm?Y0{ft-PXcC1>^#x?euJ33~_N~g=x`|qYpr!w#u(;6!Jga z5n_(z?si)7nloF=C8_>ciHC+1JZO6@W7)f>hSHOH#(9!A2BR(C;PcB7jRVWsUeCwsVr)F?OOYFxq}% zq3D0Y;Ibh;qlb4S-Jgx*U^!`90>RKKXhi!-7w|6y(~>+|S=3*8g!#XuNB*a8`ahr} zX{viFxN2yB=(iH#=@NyDp4Jp9$*93BYuXe|x9IG)<%C{$utbIutoPK2#~(jF1MzojS{vx#CJnWCnXm zR(cKWy_~+K`Aie!B-2hOUYm8stcH%L_*|wYNvuV;Dc7cEY_d7afI8zwvj0RgsiifW zDX;nTx$DscBpY2%L4UGp%2QXS6du@)O+-EFI;U2T&4$8>KSkDNY1dVDrKBqqWkWw4 zt7+6qZ^j9+`L!3Nm;&`uH90w7Nx9Xpvz@Q3@*GZ&Y5s&&M9rK^OkT>gE2X%+Ej92i z+k^ylLz#)K!bUpPYtE{QtnAX&BP9wRahN(T?Sgr^w8CX+HkLpotu;Ny+DcAtA{iPu zX!BB(xlAcQ$&NdHu^1%V8Fsz6OT88gy+!y0#s#FeORc3>_!?_zpQGji+r1qS!$Z;i zU9F{S!h(Yjb;ql%Dg(Q!6roqcJDB)S5veK1RJM zV={z~X}u=n)xv4R4W52LPqKYhJ@)#o5~w|$5iLe*5Vc-e0_qJBESfGzA60(*jEqK~ zQ&=xJ+@r~wF+%Go8>E`PCg$&7k3_Gk9Z!8-%clDR5CIP1sU|u`tDo|Or8LWxkQRrij&tbb} zcWVj(9tlDZt_#ckF)}8E0ig@a$!T*88B+qgIBjVF9`gq&k3tL^D4Ss)i zdxTAc6kA=2jUMnuTTTrh@J2^QjSp!3PyYHl#CMx3;Ccc$*40&!pmD;Z!3$!N73 z=H`8x?O0ns^jqgD*qor8HQb_y$sX#NbeHXY~_ zJ+^R4c-=wF`lB6kyPFVMRXI(9&F@30+K!uU7m|`94-)Buv5? zxfTiwJ1+9~zdx?85rKYEK@b`7M}qGrF)_ZtTB1fJyws@40qmL+M$chf+w$6vcbR32G*%N&Z7hGbW?3;UE}d=5(-iy@C7T0Yx~ z12369Q!*c`v$@M8i#}Q4!@&Dph&zd%LG-hBA)wobn@6IIuc=VJSg8GdmuCaNsS$6?;XO$37}^NAZWqtK^hK zcBlYhzfkq-Og|{q7ZemqDi}0$u{-QC=_L68k&%$Mnb0TLr3M)G#Y+I?)%BuQgEW5V zv$Y2#pZD5X){_~|;SqQKnq3!)bbubxXYbo>8o0qdnCOT)t3`wv(a-9Pw?#I6z zNIrS=8I6B0lXd_5U;O741=|1N-u8bZS8ZBehPW1K0Sf7?$;|yWlvR1+Rx8`-s60|D zXh&pm3!?Q2#yk-z!AWIJb)xCJNvRaz+l$Wag4BI8!}cD;L1krD+uUJ|p(TUM!~2wB ze48b<-8*(ggUz^5_jA5Dar{#4vReYbE_n{V`hNNxynh(@J@E``{9yh-o-q#@=sZiUd#{0OWg(4C;Y_B$MBCxkQPQP~=t z6DP1&ey~Qge-+|mydSse?F4Vo3wIdi_~}@K$m58+V0R8cxYL{H%iDE*YMhDL+Z^fB zzk7KcW+1pbqqrFIN7P?P0VTjwb({<_b$@Ldcq;l~@{H-VWaq4LwF zN?T_GW5Y_Y@%Ergm9>@vKZfcrdcG+wPr%P9F*`7Hv@Evqt@R*@41Ctu&x@o~De|;- zph{n#X3+2T%=K`)!|a)!w8TVCf9zV?8zD7e74r$Kuv9UJIb`2H&C6tVB17KYDSouH zh7{Ivnb8Nc@E{TXMGIN%BnJc|H*Q&OPK1mw_8R_7l`vMy!iGwYBL@l?wf$11%36Ld zX=!>DDS7~ylI#=6UugRXyx2osO3B?9g3bw@9ZfyG$=Xn#VT(htcF>7hdOjs$u)m>@ zD=c$wZ0lYzo#_d|g*xuvJWg^N7$N!@Y)`v4WGZ`eyqOzCy6 zOsN{HXXfFhucwk`=f_e|1=ee0E$O=u204Y9M3F&i-7ht=XB10C4{|j}){nI+Dql-0 z$XRt`2x(H&p~$r&R;b*tgj?}wQ<2P|SUHD(4&y?(yk7A0Ne9M?c(Mqm^$M;p9+?Z? z8YnVtDQeTANKFKjoQD#~6Sp!ZaaVDm$Yn3~0z`djxa~|E9?OP#6}jax%ev4W1)1wP zvTxW}McOx8P1o>#gy_XWicF1!uOwpABrm%LJ}F;H*{*Y{EL zW4evEPJK05(%D2i0W7_@7VR~gN*jz;!Q`g0)Mu(cPypM|!K$1({m2QLk~QULE0zme zZo<=oKzX#X(Fl+c;6nPX_Nc5uBIJbM(OjF?rFr~bXxU%QszxJeTr)MY)OD9-Nmty0 zuZ__WV&r!29j%OvZ?+;OvBh8xO~K2-VLMxfkdof#zN=eJSwEM5 zM4LV7iZ5doj7>hxAU!T28u>UUkg}+e7`Jc)KQp6Disn zc}YW2QE$mxN{E`XjS*YQK!e$C%U*rNiEabgBOBv^Jtj((EK=nIof@5iA1_r60a~WQ ztY$5EA)A~p<&nxKCp1QfI|rx1WK^9kc4@?Bl2Aq4M3Xu>1IZb%iI8nHRHhlEIaNSH zucXH*9L>D8&{eBD+oltKydj9@KwQpH8I4Let* z>ymC8r|AP{s{+&AjBnu*=A4hA+sOoO9qe+ zbfc50s4p*ZuEktBR%>jab_3qPlSQdHGG6RSCCyrvO5bmaL>NdwyRXdUQtA~ZKlmN63YThk1j#vnP6XQrv<#a8=t@NHy zGiI84Ry0^_#Y$GrKPk(9*}4r#zgTFOReaCE5U4$3<%)Lh5=vQ01@I<4!7}*&+_KNJ zR(`-hKN!ksk{a_EmJUujU8*fRIF7gkY7*^GZE?a zrvfbaIlN!?M~=5e2GA6Jos+FT_&iXhjnM2vC{TOqj(MHylAegwX6u*?3~U9BO&0jv+oij69%GM^~eHi-!Id5B|xK zqNnrg`PCebd#c!}qJZqDg7V8>9o#x8E5=sx2~K#ZZgK`Pgc9_piZ1bI&Xy)&QF7y0 zBRp~e>%FK5H4QJM0VT`f9*n>0n9qX=O)r>$GJvpL93&N8}YY8J5T()zb zaHBOcGum=$mS>KaurYV*&ac(yDf0%z^(9@^PPvH4LZmy0#V!7kfSGZ-EhbQ z-r5hb2y0%&#x&er*6Vq(mxxTkY0Dy(mJ5QBzUZB!gs!ITU)GA3owIUOo05FRkQBl_ zI-${z@!_K;6J%YfoXuRoa~4urHLBow^kVod*XOMq6-$$u`vE;};}04G#>Mdl?gQyf z5zY**saHR;{H z8C#gCVnXWU&2ne`$5BwNu}f9wp=6l(r>nvFAl0o?)PY%vNgk;QY91+R-f`#<9=ylP zn|p#3`>deeU^oxVX77mvyMUpNw4Y4mf+}BD0^>MYz^vZGdU^q=evPpZ7S&^pzJyiV z@1o?X-xx6V2JNb1Sl%VUlWQh7e>Bjt@*={Rt|yYksOx53>x1@?z#yMTUJiq00Q5gV zph|#PImD0o+8_0C?pxyCRy=pv`GMr?rz!&yy;X~srgtn?Ibn0DdXx;SG&DX zXb*2!HRj-vf~7uBM~s|2A#dFm8xjy9aq@v9WQ&#y6&>zS&^j@VEagvRvLs1lN8sj2 zMFEM0eeysqhtyH#Iq+#Bi=y>RV^g@786Rt4XGOM!FFJ*XQi|H=LsO~KaMJwyK|F(D z8(T>0P}U4o5m>V*d7VR3PQcUXlCHErBwSP{VdPg_qyPA6PUS*u_@VM%N5)-C#mK=| z!6w#5fF6zsRSp`3#oaw^24BK0nKtGu1pEA= zqdK-2R5{M0B%K{;_EN0UP?49r#*D;&s7%iGs^kV;J10|<-wHjjE!u+P7*P`WJ#m;W zHpir~h<6|85uKtF%7tf^AVulW##FcVF_wy)@@-)VOfullaA5y6P$|Nw`$EO2 zI-`d17sdZ>W)_w%5D!&Ib}2$%e}fsHT6I2y(N*|v2re{uS>{HlA%-0BLd%P6GUJvX zv!}e%%lf~w<|1wFgU8RfpRw>$W4+;egIBg;wGiM$Vdf`$?wP6B)*YgA;0-GEsRS)Z z^78A%PMA?7tc7{x|8#xj z^xYq?p-b2}9kqEmMrp8!dGa2fpP_!_23LU5XCmI9Ib&P&#;NB7p0?8TiSE|G*{hM? zYPHl|j6bK=G$a6SoZ+W|7?3*K*C>kHp+|BHWOp!F60+WiLoTUm^m zBQN|6H7sv{&fF!PCZ{D)n%3Rf``w%=nSM;N9D_{CazKpZN6EJd@t+QW*YR5zFV_I* zL7MsZ=%oH@mcU%j2TRd}K*T_z`ZvnAbhp7DoNsZ@soNv}zHmQ(9g($1iJ=WecuWCs z4KQYE2E+2sHC>^$Y$|zN17h~@jWLcDL3s>5 z!8Bbnks>=sI7V>m(I`IbFO?td0Dd{LM{&@%Nf<4yo&x93+Gar%-vQRpE*nc)cldX@ zV5hAEI*CI*srMPkKHvmBU2-h(H%SPc{bXESfOhn1E~&~It^b%Qqn1{DtzDaOH>i!6 zF_(;jQn|Bqyx4^q?h%#fHdr^%?y5SJCz;iwi#!Adp0YwiW2p@ASb7QOH9H|GSM|gI zlx3Kr^c9r(W92)ngAAg#*xm~F#D7VJ_19dFtv*Qm#XGNmsR2hFq37BGc}sluvRETh z2HPZEynC(@qNP_7pueQtQ8z;l!-5i|{i;91ggYbhcm{Js-*d?vzrqN5$%WZyc{dfM z?4A;8Yd@wQv?=bNin5XK1c385V0Ou-W!xdRV!Y%X$l7|ZL=fj#8EdVuW9#C zxB36n5NS|mm4+&Yhe0|G)n@mRpyr3+-DB-A$+^-lQxr|3ZPbU&5)y zl3O}2uGM*wQ+sJkdDBe)%Os};p=(|b$6!2s?hDlV3&N!`(BQSh;Am9upPnaviI1c> z-^NTpO#cpZ`CsTx%iEY)I|BX-bV*dxa!go2`<&EfN|uf@uqRFp6Q%egjhyHEQydXe z0xsAVD$ZY!EA^cuAYLK`1{|qA$lZ)uv&>4N4ARtHf)x^!>OoaYr;OgqRE^+uKBRJ? zlCRN*&hqtw+2<41Kyk-l(&y9Rv+k+s>GN^Z9R@rt2iH+Dcw=pv!RFaijO-xEzGJefPtmAH56FMcpOkysL%fx8EDG-z8_t zh}%Jnz5RRMK~}}@wBtm-*=W>+@X4ZIX8TTfv z(;H==>#H*6xa{SIo@}IDbG}q9Ig5lxV7-bdm=(2mq}cTQiR?wfnqe9ZPabtRkzs`s zP1@{IphUqqfP7 zDj^2(QcKIpV@NU=(ncgCdg-pUj+lxU=53_>%ZUQDJc`ut#0mPb^;HKtdU3})i{d1b zG_rDBK09_D>m&jv&z1u_>?)kZ=+>H!Yc`-Jp?9$rXJ%icMdA%C*u{qoKf7@uG3_x> z=smCcqUhdfJx=g~aE_i1A2%$_&*IMXZ`X?emr+47Q<5qY1afuurFRyKv<=`%T0gqP3@`4&PH@-P?~8h1h( zk8p39@kkMPX5GW3#u6`m%YqWZ6CN3f303PjB!lvSuFN?btDZr^!g^@gdR^Hl#%(>= zd+X`9MhrWPrNH9YArvkH#Qa%u_Gb!9;6h?++G*2#`;yjos;CNE*|s>OSmOdevH-kp zMO7bnTzI!sqonB(k0H-2N)t{m`{po7(BMj(B$a90^@ueMR-WLSKt4J*8h69$FZWir-QD`PB=o>;2WQ|h<5*nsAI#kEo4mVwI2e(F0sao`VX(& zK1LqGx~8g_R9~?5WA{v!tTD(PU{D zU69Ma$p?z`Q6h5J!simY98sAT*0pM6L<-M0vuBesM?)<%Om;+ZyW*MeHvILKW6G>! z8iD3xhw{7Ub`a!OOOYdk&L-p_3CpQYvuQKfgM|#H7YbS+UG4>XBjxCAl(IcF>v6MQ zs}43|T>!d3y>@c7<(HiPRg>QG`QtK(qyRmr0o%94jBz1S(O)RRf)y?S+ZtIXD(~+N zwDvrk))ZUTvMevRd}JoY0G;IJjC-$!MiG8$MShs%)aBlIk;z$$#S#Tu3Psj_w6$cvaZh^j=C#aP+L`R(;HJb@IXApjur*52#Q|OrEYjkmHb;PDQS|>3;+mDsOywhyr9s2 zFw;IuMK@AOX{kiBaVwJ+zp0njwl=1>0{+o$*C2CRbWm%pw*8!j@ffQlp-g-rKnH@|E3AiQ52Q zEFDRRUwLZzj0@PYw60E4b&*6UNz42hPOH7Lt>FBe2y*cE$HHhPh-d2!2UemtoSd%* z3GHYqqM~e6`$CyMx;ra9{p*M7gXV91baMuJ|2pUt==}^Ats}PxmZ_MOMS62cW(d5! z`Ykg8n`h8mHDe-096nKTm~8&Ou$JsN@d$jpU_1e_N>&ppWIzcF) z38jY;WrO(=a2gdE_1q-G!d4X8Q@Z-UfaSkQM%-H-^OEN(Wz)r%JwOfEb`6WCuTR^~ zuqU0HLyB?T)xW>{a~>^^LK%07t;grT6j7>m$jx_H1?88-$qr-Bu-#O1JALb5U%z~@ zYPy(e?ORS}_#Em3kNuNuL+RAj_H6i~PRk%K;6w1VOA`*bUBQ|o!BEI8{|@?`vxFb` zlH<1e2cZ}#buo7xx)^tOnz)fkpUa<}q^ds8lYuJZ9F{qM)|_@TJ^bLEm58wvXR@m-+ik`uxzz#OijAlzA)m7IqJC~g%b8io*td0e=hx`H?%hbyFj(hK;_md z0D1lZ{b(3dpGR}d5>ilFMoM_BZZ4+8<;g|mwFQ&%&t*P_{!Te&6)q7&#+S(0YK(?! z58w-%Wqg!J!vrcrYf2H)Mo(f}vua9V7I#)ISBCLH9vC+2Fbd5Hbmk(!s03&44O+qt zqfU>qNkP7a*PY8SVV!>KDj{oc;n(5KZx9XCN~I5h zc{F-A3(eXT?uqwUL)TnglX(h#a0{0a1gS;kspA>k@<-=zr8(Senl5yqH)2xSqMdig zSm5}p8|t>NImsBlM;yfc9F9pFp0d?3?4nvX^~77vLvLS8A(NlV705IW=bb|J@Z)huYAQ>v z7VpSVQp7lG7McpARF!x@L!Z`=)AU(FKb`Uuw3`F4==vVDSH3XgeK*0iTID?7eEuO` z(Us*|><07W2RP=xOX>d?+EnuPHbwvkhi^N(|H%_n{rL8^L*=E{s3oi<4Md0%glRM% z4yj2{gh5ZpL5I}G0j+6tL`kYA+TOAQ{Qg=ofs1 zy*e~8K@XE^!46y*()xfJl4q14rUf!<)oxa}lV6F^a*?hEyX8>*-zW4#u>QR6lMEFn z?T*vRv&6KSpuw#v;0-ObvVyjCYKA0wjkguN4Li6u+pf5cIVpY)RaUK9OX9X+jm<4C zTEtdsRT&r2oIk@?C;X=?;kvXYFqKxSV{*t8Ad|x4#$E zOD{0sJY*`N`bipVE14x$JC*EQWW@A$WsWu*D&lrHU~d);MmVmOpf0%q5}q`0bTu61 zu~r(^vQ?Zs<~c^gG`2klW@E2@7;!E*I8>Kq%OpiYVa9RHU&W4bKUc!G3*xe5GMXL# zS~kBF!77HiAPGh;tE|zat8~x}TBg>_p&fn^nbdc$(6 zF;lMv)2bY~GVme!Ra>woNhnsEJC8J5NixD_+RmwwR0Kc8E?ocEGR5qzX5pW%hng{GnwUc5X3~jlkaPKmW9_+-U_{pzOH2T^pj|L?SRHt7M2)j1Q&_}D-s1A z+|tPc_R_^HO4L!ex)EIqKxuwYa(VQ>Rl$=#xc&7;C6}F(h|*t0f?g0Q_}b&Auz~(< zQ1@<8=|mChv0=|ZxT2+~OM8h$nv9&|YLB+Dvv%bnVwV*^oW+*xD>)qAX``Q@`=r&I;)L2NMb-p|940iJYV;GmixQla}`5ib7qZ!e-V?aEaO`us`$y@H=?YG`^(Iv z4KfjPgl%?t$ry$m1?nwKd^0BIF5D4flkt{pYnXa$b{p@>&1M?fwOz1)>-zW4vj?U( zRo#(68_}0_uf}o{5SQXLQ zt7)7tYAj9}{sCLTNuio9;_<6}* zwuoA_cQxf)Wh%xRQYpW$(g)x#b0SsFk!yx@(CAB{m?9Ztrx*`9pf}{IzEx4At`2N6 zf>Xe2ge#>!%Q#{xkEWl0eu`E-kw&_o1bnp8AyHAYQ{C|kCa|7V5D(TH zdf}Qr-UYlgByX5gzM0mYGjCR|eEpLQJ7-}HX8moCHT7?4&)*ovf707iaEB|C%^SOwf`;`Gq?4HD}${F{_xniFU18UF*9`s+&6x>Yq@L77*5BhGkc?LApi@ zl>LD^2(cv&i@?grc+-Qh9m15gqGLyiC`90FGMRPv_H{Ha;q&o%gVGIpIe<8zj*1j1 z#(>l_0ckD5Knc>MvaVp4OsJ`&OH-L3RY(Pr4iHC|&IdU3>F!1~LLx)JGLoD9)y~pP zLK;MLAu2p+G0deB0t%iE@8}@J?4@q4p=va}5ny+GLlR7h$^loynfmTsYQ&p?u!t9%10vY0cKa-VT7VC|j9cboIsPTUlX@H{Of7o;ru;1vQdhOt#9L zyhw8UA1ErC%MXl{gkv;?@_k}Vfmz7mG24_InB-6$uEP2e9-mC#7!Abda> zBNj{9ACc`#gR>50LLj?lbVH^7;g@~Gn%&4s_?5OmF`))|gea8pn~|^>PGa@x`H^z9 z*y~M~VwR`^?Idv1PBYeFba+-apFMs(_DWwbGcNufmb>SSLu4v2-FJ>d2sOBl1PC~L z2n<0nhbncB`4$f7fLR#;A!(aT^o!skH)e5f)^>&v0$C0d2;$LqE~~5n)SJhjeQ%HS z>24R^=GrzOHy?KLV`>|b&P`y9IVHK9-qu@f9Ydn$A|x5w~8SoRFiBrcf<|L<|YGs+iVT3O=XoK4&fvm5lh4RE9-D!Lw^Cr{uX?3=L0V z$c_b?t1~!z{)T?~8v^ekKy{I;ER0esW_B)myYTjKp5kotu=f7EJG%n1?DpU*X&#H| zYu4(Mg?oCogS!OS5qMmh-28}u_;7_psC$Hr5Yylp+_giOt9Dh?&(FT5WXnQ7Mt2Qf zFacwO!8Ld&=|=ba?w#xEmvu9$>LDH*cq-&1-;nz84ekN2Vd|kDn|O-ow{@45cakyu zwDn(D0y6}*seUaAZbAiBGCHxeWJ(er1dq1^QzLOWI63Ak(;QQ~OrsMmE?@64Z$X_% zq<%SupPfAjcu}kZ-jE&|oLi~s!Mn+=2lnA%m&GSi)&f#g{9nRMBS{p9ihLnIcoHC#v_wmWUpBhJ<6{<@?nF z0%t^$#Iom5IWNp4QEADlN2xy0yYbSIK*b5btIZQBtLHagqzan#Zp~n{8JPwuQ&ibr z<*oliz@t4T6cf#hhb2L#2K=w<5`e4bAaf*HW%Mq74?1P-@_VjvY9))A!Ujb}rWUQn9O!1JV5$7s(f15iNFWGWYC$-9$@T!x z!nam6#k3VK>t?uSj|`;Zh699H#oB@Fy*TT*B4DETGfpMFbh>CP<8inDauU)>UVQ<) zRNu^`-;4`bH8F%pwNyRi1X1~nP~uj2oY;)cvv!7si8D1*lyI2^-w!Mv>AmeMUfUgu zpB*=kUah`Nr1ZF1>oAj6_gjNaOIz&q1l+rhM``3h40=5xiE~ROLw*SE-EOUP&#FCN z|G_ZsG^JkxukFtgV<;hBU+5WTPlK{2-j$)`kdyea_>MbXK8J zB`;TNp?!Argd*V*dg*$e5jES%vBAt(Ud#X+`bWE9q2er3W8LM730j#kthkt2k@`H6 zQ3t?a??7sCP{27aqKD) zquE|b6eZ3OW)vC^kuk(hABQxBc)Sx9;n&NV{A9Ni`lZ~6BEzp)YA>#e?Ea|7R~cIg zwrTK&GiujK!Gj-5{=Vr231fd^LtJjE{}iBB!c*mnu2<<;taSytY{WpiP!{LbAkugi zPj59ur#hKzo7}>LUbXS+7)&+!uU}f*EaN<8^at%<2X&mod$8k6@t>;5X1-NWmuHFk z7VKmPWp_WZu{@wEKbaj%Tfk{%6bA^G)g?$hWRYuy-is@8%QZUBSksbM`{z7>uJ-;- z5n(8A{e6^#nLTr`Ui*`>KGri0`VRKp6}^JPzmOx`nI}etBN$S*f5A13?2yCh1#miL z_xIl7n@gD=%uaDi>poq_5#@3Xvl$<>0f``^$ZcJ8=|R~Vph_N^@4u_c$yg33J-061 z5YOENWXfoF|u7@Y%b({jx@Tyi0-MF=rO?yxUz+1OAGLEgstQNKbz>` z=LCgOJ{cL-KQPc7`fk73TTOKVnCoz_CberJQN>9!WI-;E?MDlij}#4tmvF@98!+7u z70(;H4q3$J8_}jrbrSvQn7ETHt<0o`<8Io4^k*EL_!-acl52H5Qgeujd9T!{CG}=; z#`C1 zx`yC=P4iLr*Vt8Qeu}k{Q>e6Boa>(tyO_Mw@%O@AIc{HmY0oeZg8=?5EZjKPBfWo= zyu_3v+JWFde#F83yOQ^R(Z%}b*(PE9uf$MhcFo#f01vS}vAsal;F z*%E`<$`MG!0D+F={;~O(xiEqX!2<*WU=@Y4mPg_`R7()hG|VXN)8?!Vyy~nmBmXhR z-$`^qoWM~sv`)CM{YpUsRywUCw=1`uJ<_;hA(0_qz<`={#&QUJ)H*5)o^z%lW*SD} z3ATFpw;~x4ZK52*b1tKqGU=Rt2zdt*?GZjLqq+TZWYard0|XtDTP$->s(j-H$lgy6 z)ZGoZB#CpfItDh~%c9Q^xotM_!_~4b>Ary@p4lRAhCXasar9g=l|4<}DiQDZqLryR z2JssKPF;v$c0YsXK+JecXYSStstd2TNS4#=&f;JM?`Awu(IWDAExlg17 zIoxpw3Sx0FGvsk{GbV9U12GPSoAh7*9BiarX6`fl9(RlVPEr2fUTrk}c z11@l8MpwK5T))NgNhgmq0S77_vYhcWG|?RDDhJgPs|qHCyfW}g$0M02Yyg$x%LOgf zBg6*LpHYj$q*hVd^so(Dp^co?B7nULGni7c8hgrbsXEPI0n2nKDRxf3SY<9)Rp$r9 zSvt&$gi-~ekQaG1sprRN=!h_B?*MD1g&a?vOx6;>_dAZpPQrqhy|tjeAj+=_yVxBMIJgd)Z8A%pT0&rgWnTRqto6xxJ#Jgoxfl*qr-3c7xBs_+rm zvjrJ=@I}>_w@VH~H=b~veX2q6tkkD{S`#i8Zwsk)qFwJyTIkCL>xlz5Mt2j>>Q*2D z)QFq|KG(&!UZpz%M2OaCvWVzJbTZ!~d@U2VSph>X&Yz-YxSWJlI$~m-2Q6dGiRJzD zZsyXyhm-I)D?(Gk!=rB+qq+XAyBTqg2H{Yt27N5w**IdvS(}lK4 zBT0<_>-x8vh|vzzfbV^mcNqW6la${BBdOGS95s59iUP|jDfTIjDgeZB`ibo8stHPag3?`)0VU( zI(gwqH*yu7gl-<(x?2Id_MsXqO94 zyRn-;aA?Zdf>jDu1+*OwS3-=w;;uvqXz-6(_f`n3Sc$PAQnc+2gM$EsDB589Fk9q; zEuQpAny2;!B zciO<8DOq4f#KGdw^2%EmmFrzBgxSC{as+(_GDL~yWB|iJA?ZfmwK@Jb4|8;xwwH)a z)Z_H&jD61L@MG@*?nj#Z-FAO~67T}X04tw(DFjWL5rK&`BQ2U=*{c^tvE#U5lsRM6 zabS>K4Ju0l^hsvRMxKC0XD2S=wN?b8NtzUrhwb67M1c{uD~lpW7mglz6bkiuXBYf^p@phejg~h4?sA~ymLYIY zc%hxE;74ah8R8m`AaG)qU_~|h(^DALbAe*4cFz#e~C-M30IayhSIxoxY3u6?SBL#~#4ZsApJ@2R;wNg=4O znDVJa0Y9HX|8WqGf&{nleea@I|8^Js4@1LB0DCh7OEY%^LrcInUh<9q{%bP@#E!}K z3ZMpm^++PM1OV$c0di%uQ>z2S1qB6RcY_z?gB5A4tct_=(qIU>Lzv)|eS+@RSH_x9rEseyye`bPeIgPeDG` zizfe+3V547u$)EL0`kja6}9rmg$tgnTgT*cv?ksp8n%oUs~6yn;f%LRm=~?M9L-qS zjBlrPqujOgqJxLJt?C?DX>6_(q1C9|L}t<@$^$ zr&xJhSmYCf)UwHF=sihzQB1}V+Z#CX!+8ZiQ*TVMcIx(wSY71CTcg3ge2^5e1vJBW zNrFtiAM`FB&AUQti?M5GpR|b>dix~L6HKM{K#iu8wS8<(;JPBLNC2Kts1C;=*|kDp}jq^+rucH7xtBMU(V{{WzS0Ku|nQeUDgB&{nb zeO?64|L)Rq+=OEik@**pR4#FZQVb38t0pLSOq|e*jBO+?^_CQ?OIz zC;8AaIGe-fA`4%y(IgDdh=$z1z|K$1n5>-dxR?Ih$$!d!kJS7l?y}#B(0^f^iaK)h z0;s$JiM_zUR1_s8o~BC>7LoM7qhc*tN_)WSa0oncM&(V+iqlgmfqc>sK7aTm+&F8l zU`{B$F%&;draD|@aPxb6zrk;FuJSc{S~5Sc!``CgV*2NFU>s?h!m z_Hrx{dCK`juNat?5^5N7Y1Gytz#Ns>T1(@bT=Tmd(a4y@(_2$ zEhpySF^)^zg%t-KABd@3GWX@NY-nrHm8vJ|{W%O^5&c^b?q%lOaCijEJbO+6FJgIO zOJwSWbq@DJiJ(*&S8ceU8Wyn~|q&7HY#57~> zO=YSGdr`zpni_+GQ#LqT7=E)q_&II)a3?sln<+h3^L6=Rb^_n08gRx)6ID^}evQPy z^R|^dTFr?bKC4W^v#}r%3k9HmH(E9;;GPhe6MmufxL4i*3RGh}s}AA}xl^}wtr#4@ zPBz!Byy3E*cb(gj#H*UWp|V!!SW~uwwm_}giyo^&iP@n$;O+HDSn2j^s`G=0{+O;v zPa(8KGQsA)-YWk9{>Pq?^$QW{;QQIp{RYeb-)S_(zkhaA0rn36$A~6M<;($R0qsji zqAW2%W|!yu=PqK}bbk_ZgyL)HMZt1(JjD%h6kc1dya{Q=h10$cVMQF`F$lB|=*P4K zKWgVb22p8+u%xz&poNM%&R1cZ|ms`GEV$Uk1W!-9Hwe)DeYcb064n7xvwYQr! z!0dHZ(9TRVnm6dS-95sAFd-A5N6=ly}Ku zZLLAEF5vbx(qXNXsz7z|)Vof#oZ>utrP^jK=qYlElN*iwI=w)KC z$rARNxBR`TSI~a*SxXORP;qehz;6CypA>W$W!El4G1ij9VOlzAai?kW-{}6kF`Dp+ zS@~!-l%Ce_AzVlNT4?8Q`}f~vD8h&BN@d&tZre}>kBSqp%c8n{Hf^qqYaae;5Hi_f z5UU?P^#*Z!9W(qR(Z;5Yk|`$?i-O&B6RWJ5*bx2w>Chf&o3vA1dkwF4hNjOWX>ChZ z+E*-52yk8FIo3vmRFH{_8TySz;3dkDkKBj)>@Jm!Dy%{;yRt+OP=AM5LMMc^!d18R zOjXNvT;P?quc~WA!zNt&hm7YcR5M;NV=xw$E(YB>@Fb2JigW-muOX|Jhf+AQoS5+> ziCdgGGvu&_;L(B$BGB}58o!ou5HUsFonaogT_45E|EW>V^s&RyJl zbneb`G{~<<^MkFpm9XRMecmMyGb1c@x~g+*V4t#dGaRP)I-~r<5w54Xrx4(UN)e zP%01a{9Xc|Ga?#zNRjue!%Zffe)?x&o+wOsBScDj zv1?e==-bbj9gWd*)R%x+#g44d#rT=gtW#Eu3b&5S{1_#UC=OAIM14VBG-?;uYjL*R zd7+XR$3S4SVCvzE-n(}&U0A0ZYCMf*3`{Ar%cXt8G>=$qZHF#{eo*f+C!e7&5+@(} zt7KRbN~98+3hB$JLwuqn?)D&8Yg?GG^;lZW7E42z5$(li?9z`z2!`50bj23ST2k_+ zKj`gtQBRm>Boru-8Nb(gBi((G>LS@96=oSThYA@CsbgkT@$)NqM77)^Mcp9TB`2rX zFUMILZ>3?C9P>R_eCq&WH&N*SERen72}U39c2@$)bH{pMQis2dG8~##8D*P%B5C{%pYvy>JgxE6q3<-hxd~ z_d+Za@H$JR5$q8F{0@B%*@6mm}a(O6Sl zRUOr_TQy*lAM?)k=p>e65%I@DB*jeR8KD92N-v7^`*t?n>ih~PcMA}Uj2HLIEcFJy zJ;dv!bB+*kawzM-6O|7)+T=dAZ4jij+rldL=zxnpmlVGiAiD|&XYmsjuSe4E)dWiY zjmwOy5j*u7A9>>KA z&`64>e7qp9x|DU&Xj$SRPj6H;6=nrf!KRu|Dq$kp^EN%GW3CydLl2y6(tWS!bM1Wh z5_=KYr1v5T)9tbpp91pxtZ~JY@SiXyphmiT^S9L`BAmKNC)N9j$>!lCLMHM|6d$49 zfi|G>1R<6=5Zmi(C=fBW9SsNFkW{@K_X&dKA-{09Gi!T-A>??0r;|8?X6{tNo!ey~~M z0|y5u2A6dPcXkGc7X^Q;ozL4Hf9DYeXN8l=-hbErsPEs6o6ieh$Nebp-#_1d=lR%w z&r|+j2<5U61+UzGpUvA>ZW_-}76s=k`JUF*RW;NgA`XByU{+MZ!~zxg`{00lH~a7r zU#l1|wTy{{iIsvW;2Rqh>zC>W)^Df}7AFmB`OogR7gZ@v`1g_B^8NY0hk9Nno!Iw{ z|8xEP{`PO&<_rxS0Q4aLT<;%0`K~GcdHvt@{--h7f2b1wx!!--*V)($ey{$oR5MfE zLtksb?F&z;(fDQ1IdBirl3e!ZU=m6~GkksEk7ycl^AE_RJ(?gQ)RR@!Kcs4{l7a;7 zi%$V!C)8d-8nX??L8#P}ScPkv$FG_MT1yoht=1c_-c~_;gQMO@>t|&a8c1wD$LkK4 zoJVh+TTh*@y95Mpu)cgX8zBr?*SmEOnEiTgH%qrK?7E(^eEe4f@wYDt7UFZy! z_Ak3e9k%Z59&POR-t3=UTHX;j-v!{mR(!vD5f0A9?4N5e_+zKCM|Vu@pIdOf2}ATo zchu~k8xX#tzGCaPT(9dfrk<rzF;kNLHF$p3xqiE+rG<{|$MsvS+J4sXMyk zy9e*)W*FwkRz(hWS}6%o99#g_Y9$^^Y%o#mrY#1R=_`%2hUP-zPgpFpuM2Z^XUdWq zX?U2m8ZV&msm2Zwx@pH>&o{Z(ULQVmb`bg1L7F*FCY{(CAH+%5#7BSG)4QHyEq`h$ zyUK2U-7oGb8&-f@y-0itYOT$8omi$>N@XA}SfV-{mvK2_cMRRbv`jmFHQ*ow{X6qrudVWOQ~mEjONIJ5V)Y>QtH*+MzYmZGpY zKL18zwXlk6y<{>WLAFD3b$axbii`QwdSPC9hYCjcSzR!j8MD+a=DL=S(r(-;^#-+G z<@CVy-L$aUQM{(H_f1^*avo4zWUA2WrD<|`?Wjk<#BN@RK+CX^Ioj{g1R403N}y`mqOBqq0vcc~>qTrTafV$9TKw_7Z*%HG!5|_CbL+{)VnIW>_SQ z;4lY~OvNU}dvPLGF2Wi^tm(xb3x_Rr8}5&cZA`FDygpeBG78XyS1MukDqmhRmW0Xr z9Hm5^CGdJ=6Qw(790R}wu=7|~JO!SWN-Hvb6oaNN9`xyjlT0w_Z~H}!)v1&PbJ&&f zSJ+Zl#t^v%+MO5buIUj37w2@w_mRpXwI+Vrjx!7G(CBa|m~*y2JXp?$3dl}xuw|Zw zL8RKrSZYRzu~}zG(pcMZmn;lfn{LQCj*+}&w0~;y7)Ft44rszTxE2QE-ljDpHko4* ziqyy=w?S6T38$Mb2}6v8KDa9P6?25b<;@YZnH$D8tM~D8RPJnW$WAemCj6x7k#msK zHiW;5?}Iq7ts-OFOCW2t4W|twqeI(BI|kyl5~3s(o;CC0%KD99bnLu|WQsT8%r$Fa zqL^*SpOny;hso|cVeNyKOo3VISY6v!Nh>GT22<*&cEqE$Be?(gF~IiS=+ z&0Vb6(mpeimUOo6ER6y*bB3hFQlp|h=({zWTC8LeXHG2+2(wZgZL?*kJ`Tqgg05M& zAF#G?E0oA_0fQsWw~Jw#C2G{CVDwRObdm(++xRtg1go-WZ*%9mk}O(MpLwL z<{W`UXk7s?5SuYACGK-HzdshcCss`}tM#eZP%(3pr5LE+P~?Bm>8kXTdg{qD+veWE z&|>3+YU&v)kj>p%FPZBai%I3voR%z%WiELA5{X(UxH`&cnKQo#Dfm@+tSe}fdeIr8 zm}+I~` zGF61dI9oLdCGAd1S4NffVz;(255G=RP<=Hz6}{p)%J|7*Ge}iBCrZSNE}i+&#kz!n2@XQW#ntdI&VC zhy_05J+jt#Ux)|j@M;_{+<`Y-?w_MI$Fdt~f1uJ;`UUtgBLewM@Mr^ddEhqy`E2m; z0Cl^|+h@&B(7D4Ev~~M~jE@Um`LO*PjK>Oy-v|hy0@qdlOfi%d z+K}Ml2ehqsD5W6JxC8r~((HYa21NTGIC|qg7>(wRM0A*^)aQfF&y&`DP-#WVsk?m2cfHtx7C(RvMso68yP85HB>!HQMlId0 zR)mX}xlISF%k4mu)M1NVQ$?&hp>y(&u*PYxRSfOe&DCimtmqm;KZ-)gbji%csO_D7 z%g;0nc0WNNKc;rfU5r0Is@((4EP81qS|s2Ii+IU<0bP|5v2hK@JWv*uMarDb#&0Cl z4F->PQHX4t=@)gUy&Q0F59o9x=s3!}&v4l>e_Zsq!5@5mOWe*ZvW@=1sG=}NV+Y|? zDoQy3vs8DCJb}~fPz-lr=bqipyX&j!a8yBR%Z=XMg4#@nl7ViUx~wT}Wpg*{_30Jbo^w!}>?uH8` zyribwQ7pawCbLa>T9bk)`VXnEb7C782XmeJi0T6wgFn;Mc{TF}_?joq*tpAW`*oEt z@sM0^K*))13@)Hk;}N1%#)%Wm5A1xOI2pjun;*$flt15+XJ}i76kB?v)}UIy$K35=_r9&@O2v zJ*WO2LF0*%y7vTBSLRK_*v>&{Rk<9s!+yn62I4QA`XzQeh0_`!*K5@x7OWSWzBTH= z-%4)iuc|@Q+O+)1RNB^*UGE>^c3ucVe?@iRH5jmgJF*8!fkiCoQqL_6b2OYQu;J;adw?BU_|a|{yI4=4Ba}+F!UiX;_ybFz=<_ z54FTGEi&FVwn{n;-TOK}%HYA1II%5o9gabu#&P`~hd`U+IvlqrAy1)`mTj^X%UYd( zLrt$GqT>#8V#>)fw%-#Xg5e_?vMQ{!H4z=Q=Ytb*4u?HhR8e@@tg@b`qD!>iz4m>^;5Z0#5IqN0LT6F9Hh9D49D*G4F(G zo&hYQUzg$tIS+uD4~nUC^s+?XDCk1!CDl%TEDoYZRDozx=k4j^)NTL({zNbQ8}8@ zmQ#tqY?_r#;F&+^+3}yQeb9R&b{t(J&$iBX)rFR=P8wnme3o(6r{57(8j$PtH+=fB zeO(&YT`kMBXSSc5Ml0ehO5!5A{6cXdrnvmo!^5g?%H2)r*W>Vsoly+3&mX(H4 z;uf2hT;jGescTTBc*Tm7KB?PebmzhS2J)7l+0nCEfMK(;ympQUyj6I}+xrnV*de!_ zUHal{OR%~uc5Y52DW&u!#{I5uz`tt1#r+$=KXYdyh~H%2O>Ppp zvp$OEo@UF&u7qN8fKJ_nJp22=nL4J)K2euM-8Bzy(r~DHM~Aq*4i%vRVZodZ?fUp9xDFFZsYaewYgUC?vOAj_jY zPK6M^cEkC*@v=d4fLNWE2QcJq2p|5IHif2XrfG;VpY3JAPtYXdVNVN87^W%9O(0{t zvpry!YDjJ?<~gPYWecS^lyXjurkj5M!)5I)n(C(I0WEZxzw&wGt!hxe!2$IIPD4e1 z1l!%f>^;r8QHZYM+nFaCYuQD6ri=U@>9ol~X2Hx|#k$P_P%lJ9dqkE+xo1UURo&gg zJFvO8!2yQmi->Ax&nSK4b(FJfQ1TphSN*jV&ATG_BUwz<{1II15*XIL8tWSuw3d!v z&RSPxeur&hnVVDh5v}Fp%SI5sp1}byrz07Nl#)Xm{kFA1cM?-It6O`fPEXJ;{@z3` z?aP6ufb_Js_fN^2%_|FMh#5T#C6pB3TF?n#!U>z24-=V zoPJ^Ay!jB~xHK-EPv4<7#Um?2pZ1EpO;1bnjmjj3m`5^t=TkE?@ccI|-@|Y_4(iUZ zqGZR0##ROFz1Q?bwEKFD#j|`9?LLcuQ_eYS1vmg zavm^n>5j8rl6i;Q*5-52-=Z*jUCRrb2SpWHxK0;sz>3bu{`!2B*-NlUwjN15s652}eOyS5rerTV&XH zy`iACw6!*P5x`Jjo@fzOR^5kYNlkN8UM}oaj(Q)iFkoWw|50|1F`h-ymM`13ZQHiZ zE?fVyyKLLGZQHIc+jf_0`n{RVOXkDNn`D1J$+`FD;hw!YL?XP10b2s%a+J4|A`7 z%_d5zUl72oj%dJKRRys~;BeK!T3RIoCQd@<-3J?`nV~dW*(r(p-d#Kwf80Ak%ZU`D znAU4!(e3%fgpJri${##67p<1^Sq%R~y18SmlF4b545^3=;(^zW_AH`5RXf3k?Zazy z^w0*`nF`kUX*MBjWNlsYhzf1}qv@b#59Jd%^xK!P_%nm5R@8xW%2YmhY_l8x^-3e+ zDw+IqWo>x%;0={3rCL9O$~D2PyARV2i&-ctbW$G`$;l2mL9L~`UHdC-HwHFcr<3Pi z*-?d4d38nm1fer4NT6{X>`7&&s!Bg65N}_dvFT3UgKo8b0r+j)>FD`V6VvAK?5fTM zQg#8j^VrEGK2kO0ytS*F_Mb#mD`a!AU+Ag7xx4pJD%BP~f_@am$-YkF4qeJRgaUlo zqU6uzOImB!ykrs6?2V@L(IZvr6@h&@{k>?y5O%%+5{`Fk>%uGR{Bqw>0b+G;p0<66H|t^`j2>##811e-r1%@USO4Dl!!HXhI=8G%pv9Gbra z4Xv`Gx}kDGV;%tik~ZmYFlMx{i^DAEAsH;~#J^zePi-=nEXm^bG0$W`&(o1zTs_Xk zWxD28RASU-uLo1&B~dcigUQm?A{A)oOQEyz%8^>eX~(hOx3BG8h;%F^tW(hSYcO6D z`3N(WWxKk3ghN6M<^9&LZRin}rEaj`hB|Tt{OdPR`XByI%LUsEol-tigr;MfM4qiyIE!Ciyo%aJg6tvC@8| zNlVdy(K%p}7k<`xZIN_AC}VV4FqB(%G6xS4x4*)X!lNNnR|G6IB&pbB%mKAqDgS8{*PFFfyK4ryQAH z)0Qe|7a84EBl>Uueote9B({`$zE3NmBbKk9|}H=PqV4 z6zK->PEyWK{hPK|J7jLpImK|yyz0}-50qk%l6qYP!UQ6Ht=tC_*%yIV)9jSE$CjHNp;!0V zJAC+~-iTH!XY$qpmi`ksMKIj^uIH0D<<`X2vVNhI$b|JA_7g^5Tvc{hOa{*8`ehu89wPhL#Vl~Ty89Kn6V#ZSXKq8x9Hd9>5869(}lSZ!Q6}HGhF557Vrcjn9v=<{S!e} z5r4FMd|Ndg%>RnLJLrVdm=^k(p#Zf9J8HEBc_pJV_wQRPeuz`P`b7PZU5t2- z4)u!_Ub5^h@CH);0w4S2B`2(iPKFI#B5B@s!A2!9jO1kIF8Sw(t7B5&fpMIl?+w~u zuC*@el?X##)eYjDUy)XZa$v}@@4G{VaCc6BuDl{{og3^^elvVEta!sMAT(jp>7*Ll z;82{uhH~l5+?HUVR^g?nR()$5wgvPKp)++8M5OVO*vbI!m3bBPy{HhUd%majCR(N;cw>D!MjXfm=G%1pIqSqYIRknDV`B)A*w|f z?$x$8Uz-+{>EAExT#er#lGB$cIzBGV%MY(Y^Sdi})tqXZV~Be^c*+ih8k&Z>+B&;x ztM&Djjm_=7-4)f{z1_{d0AK?iJDr5;lEo^0uhawincGKjQDcjGkKgEx-?S#o7m-1k z%yF=tHcp)SpHm;()pI02d4RdsBzf&{tUoe;50spi8r!>D6`*XE`y;bkOC4uc0yULu z;3#h@jW*6<94k>|buOV>#jA}l^OBBEvy?%p(ewQ-FQRNNvoMmUB}w~jIw3IrtYUDI zThQ<%rdbC= zV&Z0=>e{OEPOjW3=cG40$FMZ-?z+kxamkXJ4U1%j6c}N_g{CY!eXK$qB&dD%m|&HM=X+&$rP=ZwyK zOVIuaN-@*xxQD&=FYTzn-{+NXVZ9^I1__yMp3hcV&Ws9Za^t1F+dIo4NGEl%JNcjK z>CDeCP!Bh)q8eJ$$%5_a0!9*-p)q-hqoPcZv2x2))U#puGcBZ}_{85iv#PaT@37}0 z`BWC_9wBrNgddqTR+>T3$PrSWs7n5U;yLGf7)*7f(^DWZtS1^dc$9;&KnLTU&+`P8 z@(7v_G~&`C%_}LDv?(7X1bx-H?S#hG*zgR(c0v2mq)H~?eXGih!##Sz9imQoAKn-*hEFyk{1_>umCGQ$M{6O*!SW_Z}LB2tH(dO!o1Pq5b58g0eX$6}09JulZ z+MYx~UU>(9nEHRjUJW5$d4u~n`+p=}1tDJhpnm3net<;J5u*M@FbAQ}f zh;Xd~A&PK~03k|nrIcVFSrj&Azrss&`XH=4iL0aE<59eFDBp|mHL>+5$xHA#AF*ok zSs2X?gcik101?r{SR?#OVZCHxDqDol#u>4?gh*OihBpT}gla~zB%ePq-TqZ(!1t_n zLC!ViCR*rhi%8}w`aq18yaGzS>8QpCw-cnh$_491+=9D^a9|IE8*%L;B@ENp4_6k1 zA4vpFGe22L1bRUXsq>4>g9y<_35auutR25}K%w}XmP%rlCcX;iXgnm_V-1P}=8v;= zcKycx-DET6or@PI4>mTnwEg`!M90>_~YR`SY${{Q#}Ztdud3FWCvO!bfyas zG4rkqR45+OJ8|bB)R;sBzxs90R+2KZ+H4NlsZLIukPc$8z zLjT2`d>|cN4L(wMb#xJHldda=6yo^_frd)L)}p(%qWH?6(?L(Yx)=hMR`GzJw7WkaEK2% zx^MKO70HAwH%0`yVw7oP#LhwH16(PZbOb>yt@3ns%@m!zVQGOFJREsS2 za9u9c1urwUfm}b$0>K(;4PyCaB)`$Hu$8mGmNCr5=cJw%zKrJ&k@E5d3al;~Ch@Y4 z24#p2RZd7o`3CMWbuCA=)hfR@zfCwZH~q$p9;)+$ajTd@E+of2!zt7e=K)9r|DIe( z162sfCvKd7NSKkxQ>89^)yY{^ZcW|AsJiqdcst2hUpvOqgSIj+Um!5Vu06oWW;S|F z%-PW$c;#g%tXs&wxDcK*GHGZ{ML#%(D|CEf70&6vo+<0a`b)Yi1T%KG>S|>rT`G0> zWgp{%I0>KWE(zZgX<2OR7j~!XjNhoVkuwH;k|dmsV@Ig+=4rjNYlxG+QEFIW>YJ-v zNX}TMvE?R?9ZlR=cDG($x0uckQMPSXN(U%ihBpzzQZpYNmi&w2!BZJrBOw_kYf*pj zRftFRQ5A|7^bqRaVw_e*v6A>l}Z1M8Jhl_)^(0mw$@p371L6Q&nMv5>~(#;2SFW=}dM7 z%XeT{xcdV`e~23-1#oee6aM>N!hqri*GO+`21akkLyrrN5K_#2VYO*g`UoZGzz4^w z66uA6bf&!~n?Wz^Y3}t)0WmuPKw<2i-wr7=2xc?-iNCh~7t8M3B;KY%xYqoa;DL=a z=41Y%MuC~7i+qAi)R3=?=UOeB>sK(}r>zzT!(y)!J*w`^#<32hX}A`FaWx$V<+xbP ztQU9e_|_sQI<<2{ns|<9!eaSw>2ID+pp!6G5@I0xko_$P`7L`X$h6A_W9~~Ng&;2j zxdv{yFxGU$!!YB$paz9#u?;Jy1nL84uEYX?sIU{L?2u&*qscDGwU`Z1G~ktT%PG}? z!6l-YGEs~+tV9agEk<0x1PnBO7<~LON@NJyPD8Wl-2b zUMsq|P!9nJ6_4IEvq%m@3+^cJdxn0oI$6{HW!)ft@uyYeIW!(;YN=_S=uRAF0laD` z_sF*e5z11&D5{ydl2Qe-&#-)fE0B?d{lkH2*nwF}A(T9*3Lg~HyNS>(O5HNL|0tDI z8N;OGlQd#m3`mY{PY%0WbC}y5q@E9#EnkS6SWajG)Dl5h)~R)fj-#`#)Bh;I#KLi6 z(F8d4J9yXpFBnsW^COXYT{^cD*C7;(M)pj!86cVdNm=56w1Hm+_DDjV3L-pRQl<>c zo4+x+qoJ;hTa|P3<4l9khObdtmujjYXIBHYzwFZ$w3b5cJiffZaJ-GE&3vKFBeYgw zPoaXrk;{fQ(yMaURM&hT^`muJP zP+wKjXXgTp{TS5ljvX-^LLhokQUQp~ck1(_oA465R!m;FTFXZNCU?WZ;q$MuZW*RN zX{OyI6o*5i(Ygo$vKBZ2=#U7)&gr7V%|xU4<`#Uzq`ZEnl)tZBhvNYctJmHMWja6FkL~^Cm8E^ml7X;|{na))Z7URVT z{)2T-kmT;5+AEJ zpgD0>>U+a>_yY!2txW3mR#XMtu>BfLAJrL!>lFGKye-fZHfK(@MRfXoA_Td0=u<0D z-m;7qr&Z#US}SuOi$2&=>+)#g6E!aXOFNGmT;pDqJjJzs4Yn&NI*{%9X)BB`Ih{`? z7yRXMhqV}#;i!qsAg|0ujVb9yYGhr^gfPp4t!NP7Z$-4zJ|-&vFyJ$9$2Aav1qx5~-- z5y+9izW5X}oXP+mF8-V_F0EZj27g$55ET%Fs zN@qa;q<~=6M2Gp9fuPZ#8fCITpHL+gsBz=dq*N#s$pwkaAk%4>izKnYs&h%sv6l`o z5!<7}yun=qev9N%1WyxVQa}?YXj^C@Y)f62Aj&th7iM1wLEPJ0EJFC2R99RwY0 z#f`MT!BQ73V08;~!yY8lU=^zsMPm$FO_E?$(S_d2;7^tVTcaKI_t6sXO zRj6;3foA4G3rz1<=6iY8^nU;deSl@)iY{qxMIWvB;xcL3u`#ONq zTTC|+fJ<{5>QIOhilvE++T$Fu{l!kI@L)Ye%A$L^643yhZ1ziaHps<{n+px|h??H9 z^B|PO41HQdV-PzMa#mI2Qt}FJ9Q|_?d4Q)+9;dIc44>m-o2q4(3^5BHqiK^FpDk=k z(-XsOj0h!`^q_eQyHl!B)(rdkQVB;*Y~XWJJ0t6?!xcAre!Vw(c?&cv{C>){PQ1hQ zkf5D1nFfO(kd_MRZ+}XgD)HK`=-jF*>(;Rsj4;1r(3)8k7+e>TM^So-S!s-mJ!fLj>YNRIv9UY+g#|tt^(C=r z2T&<3W}yMYU{eS1+<}Bvg&v6hWQk)#BecB=LzvxUO1*3c%=XF38AV-4TFT5_E`Z-B z-!`Bw8TT2E2zs=st9Z7TBYSorpCIV`Y>JLFIarU}uQ)MZto+1YpS|e_|HqO_A3f!8 zRNk^8;bF%*`L8_kL$CJa2L^LRT(<-Kly_HhTYAG053_*C4sY~BFTcnRr>y5Lv3ba` zU2JmO3890q=Pr!ZB*(aNYf-m*Kix4xdfgWE^W-*G?+tJrfKf01DsU=!+c6A4xK+dl z1ex4}Rr+ESzTq%V6^lcb1$1vk8Gp|2wSC0yi79A!Y8zktrNDRTeHTa-7a(+qf5s5N z08>FEt||;AD1uq;nBeiZgSS~l3no>HI-EE7fk`659xL*1;{K%3lUEN_grrgJ1C~S( zX}yRio=!^9dk96_R-Q(*qrx#*lmC(@xMtLylI(`B3uKb zoZb2gNCvuPdP()=j?N7L@gJ+E6s^}5}eI5h|SAUT_o zBRD(#mH;{lK`{wIQ3*jwiN4P&YBz+-|DSfzD&yvhMwo+bX zMO|z56K6eVvQ#mds1JjGl_RP!Zzvf*f1iZdjPiqKJoJ>?@rTcFz0GPPCFn%nI!yi$ z$-V&}F2t3m|40fOQ!rcRrCEyF@b~gLgx&@kxJ( zyfrwM6NtQ!D4Up|?F{2j4ijw;!@HLk@Q{m+)Y2K}LAsh~5y$w-Oe;Mjv442|@}C60 zz%MqaKFE^@!luYNfUC@0ECX{W+-;o7(JjVSi0ODFwpH2RCpg2RqSqqSKwTS z_g0K)_$z9gx8PopNmfc_hO~68jKSSbgMt6YN20 zKFEK!rfhmRu7+w5iN`yT+Jn#7?medReD_E7UZe$7jZ(z;S0;BhXjJxCGN@Cjo)jB= znA(!m^tz;Q?KvBk{bZO=66$Ey=pdVFK)yq4Yg6qxiCa8A3L7T$ACvq0q$VzG?d(801#yfr(kRyW5fW1;ghmgD zMh}WcEdT&R9Gp8+d>B4n9Q8{QWa5B?DeBRN^d$bCvapvfg9a{a>orACEJhx)J%(Rq z`3$#*gu_ejgj_d4;ovNZ4rTK-{+O^N(wmV_I`s#QW4-j)W!bMN!#lwfQID0Q3Nm*G zBlkgf2LChm;%P3pj1M;#z)}0U#ZiBF_-4n%!gdcnWWX3Id0BQx?#0*(+Kp#p8hVcX zu0lO(9l_^04X1kch3p}I^ixL=uJR6|_AZicsRPC;*D`Nh3EEcy+?t-+UsNjLG1!c@ z!<<7p?1Nx_c<6M4JULczL+kzLoF6T}HdxVx+^3LsA5|W@P;zGuEG|$ri}o?6Jt7GO z5I11?)ZbyGPmbB-^>QhY_vW?NLKP&WdhwAlH%Wk**KheyH+%yWQ{gCJ&WAXJUV(&^ zQ#D@`F{|NXUFeJ&qAe%RJShOj%eF@Wyj2UV3AN`1-!22BI}Y4<$LaG54|8qRef5pN zvX6}wqCG91N(ogQg)cB*SJW4 zfI{W|J5>tj|5%kGZ)I;~XJq?d;*=U~PaoZt6amRpdTl?qZVD$`%1QevH$0KGY$RJ+ zg+j5`ptRs>iBv;d^2Q@607pKU)EbN#J(7&BOu+PfF?7a(X=baCRRL%Y%TmZ}vI1|( zwMzzqONKp?RNv!H%2PEL>|>VT(+=<7ZSPt4RqvanDng%oTCwKec`gg)UWN>WM~f6Z z*T%-PBO?@PbeML=U3_L!sC0RUr!l zGDQi1I%z+GA$K?lU+<<6CYOX19Z-?HPlKIBqqt-`O@HK~%`G~f0mvXfvIS-t8*%Q~ z7+`03sF&PX;ptGgc}B=RErjseoq%tD^ug{PpMVW+Uz>o<@rmFwpgBzAegjY%?jK1M zU~qdjB-KqHll!F1%PZKs0ixy&I_O*hRZ(g80LnTf3<1izzg#7_7pSeTZ`*g`YRjs zCb-L5WoEXhNmJ>k*XK`lNeP$BQWn-n5F?)#xv};^Z*cud0T}h?51j5b@dGi^P!jDesxA~oq1Vs-Y{Pp)(+37&H6EXG(un!y1D=+53jqzzi` zE?ERdnTLoHVK}W|2hjYjHdI^n>_}S}aS}Lz3+X%cD2k)q;&S3WMsg3K&q8vyno-Ue zVI&2^D5cdGne1|I4$Xvv6tAMlJ-(*pigB)x?}J#>FOoq4cbP(TWX(9uln|m$rBQR> z`HV<^a5W%}o%mNFltlv^VI_W#8eF4B%+h-iF{3OEP;wUD%24U8tgvHNrdGLXDiJCK zBe4q!Jrk(G0 z3%Z!t^bW(GW^?Kt2K>iWv9L_GT+GoWlD;-W#KMzwbE6G`*+J(FXEJ0M&gLtHGl`ex zXd+~!Ey6)yDELsGCOTu^{dxf;D9$kmiBKfXiU*MPSOHa#r9i|un2uHV^FjPlQ;NBe zqT$}S&gH5?XEG#vLg=tLl*{nU#+`HlyclvXC8}u>65tuOh7C?Xz*-=bE#zK!@7<4* zk5DUYQWRsL6zntZv(W>#NpE;$gUWLyJz5&hJa!Dlsv9w%H)>GBnLu6Ab5h-2|y8+~1 z;wSHt(EXl{PB>-(gFP!4dmB287-{q^Y*;wjK~@8#Qpry6I$O-p>&a2yplTT!gyaMv ztxwG4Q3?(eIhdZ4z+*in!H-D1(T=z zQQ?g;CqV`m#O9C5_}E`T$40KWE-g zp`)9@hUsBQ!&cyGb}L*YpM@lLTCY7)hOT#RF(z=b=%uGwUvZ<*MjRrBOK0Toouj-S zWusj}5B57n_pUxYqnzW%q=Q#hzl$`_*<&2gfW6Ou2RSUhn)iYS_^*(E z?n1MrF%Z0p`vf{m1kkVvgz?9dDuCNIEUlC9BX}9OFJdo;kkWv#+~Fn+2v^@hc%WZj$iOOO(S0qNeSzo-gmYgcehTyJ8KKT+6kl(z7p!iso_ zn-mqVmY>6UiL?B-2HY)uFn4S2$6g6aCyTR@)x}3)y5T^Sc^fcnw>p(s-nU6rO1FFy zWzM1?S2mT%un5k_Y=&h1Muva9+Lud)&pHEpl{Q zIfm867!ZkY-{@!xBL2v=TF58mDpQ{Hd{ zNAtu0_hoF~&BND+pBc=;Ia~QT{QA!``UZK56oSBtF-c37qD!#6k|YuH@!}|}zNzPh zh}-Y1Grjo;=-v+@&5LUhb7Kq`@E4=GrVDg+Rb3rTU2PprP1rFB<|_E~w7NapR3=kC zHQyyY*E4opHpgMbRl*A#I8N1ai!}*;MPMOH=76W`wUk3_-A3y%Q`y;}(xWJ{59ae% z9cBG@WXqG{X$9WxiRTP6*%~^%rK?-3Yr9)38>{Q9kqji>tMmf1?^tuV)$AdvyGHFz zAM?zv(2>1LN<4=urcN;nDsaOjKfIhmlE-DQCQrP$fCZ_O&?VW2l|)lc?j4e%zT}|%{V&Jjn0K36$b`uUaZa~neh-$aP}5Bb0QM>ydX9V?g{E&~KhmEEKK=Y5l9-?8<1hf#HivI%O^KBR@KM zjy!OpLT5No3u(q|XqF^uhpU~tTc5}l@e#tayTx2WdTGzqZ`*@En<2 zAm^l#qupA#zNm$IztYW`UOpk;k4%Bi-s3%4&EK?fZ?fxoB2LE{wyI|vxTNJo#(2Kp z%mL5Z?dxSr6dQ747FEob}bA32KG*f?8>>F z&)(an)#Cn*x(}YkXSGjL%x-7BS1Q9q(i3(+sLVh;D{@I2fWYt5*$Q9h z$hfZHQfQrC5iofca}L#^S0qIBa7$eRKH0XoGu4UP{TdzD`WLuYU4$L|F&Z%P1jJvC z0ZWUlLl^bcQWsBpNyKd}IpGIYqzyrt#wfoMilc!%%m6~{jiGWz72|1%9d|E}U2+AcZTg5ytpeaL4oI@Qua~%!|JlQugvH&|i z-BLh0PsMU0*%=m0{(8wmnbE$%Kv+g6V8lyFjaAE^$E;mLE^Q=a@Eyx6FXjy)>KbNj zqqhIo+v~d4Np{Tf>|?_xobkMQJscHivWLXPejUHWY}tgB0O3siPmER{rk+C~iK*@e z33-J_qt39jPh{k;XXb$)FkcPm@6yPiW0|xv%RrW8RjKfzmk|(=15!-Hj@%+B_&>mt z+hacQGD1iAM0SLEwH|8bgnVDyXlt^_0l{S|{h8r(xzWWHA{%m$?dwp(9Z-p$Pyr!O zf#P?XuyEaI^@+ghA=u`3K-nsGEP!We(q^$1lF8hE{+V}lA;D|?%=mxE%d|P5)Z|++ zIw_7JHlIx$1RCM4jK)AMjAL9bBjxy6R)1b?MU-vKIvRnEB_ zW)_(-){A54cS%-L59FJ559cy%9)r@X5(7RHvO`x0g8^x^D8n?Iu2rg%lRCnLWI-A5 z>k-ro$%;E5vrR;F%!;vUQ35`6!hA2j7?p_tQ~Py9Y|xUJ7r;l9T4+6OTc40w5!=0z zIM015F-`b4?5ke@%Cl@|wx7KG)9xCfpJ>&Y9oId+j%eS|%a`}Bchtx=L#5%JA&z%} z#b;2mKUSSNP4K#mU}+RrvWCS;d%dXHePV_j-QE_JuUpcF0d*zI*KNt206j$&Z{fCbvY=^b;d@@ae>ah2knzN||d8N$LsTqXV=2Pfpl^eTzh}HAY(VIQU-!wL~km9=2$!XP3c-g=~SnXf^u$(_rLpsbl-kCo?CfY`xICOBe zFWgYKWiib1>)GeI1(xEw;m`8hdT@2|>3VW)-qnBHH8;;nfII@uxH*pykH(BBJU|ShO*8vZe%^(V;*Ypw;Rs&BQCzEZ*Rif z|Gm4eKlKKwMm-Tna^7w8clHXU&V~gJRH~^|s-tqMy>hxs6tL#$(^Y=7(Uu(l^7@j{ z@u=J8_=T6^e))_hKuf~JM%%PyNS+W#uh$@V3EUH1VHE1^$fP-^r`{papF+KPV7Wz+ zvPxLe-xj)dL%wt&WH|A|Kkg*gq|SqwRT)Ip7*p$x{HpS2v1K=HP{-y@98u7o%(bf2 zY~aoCWGd%a&^AwEo{DbzETX8$%w3n{=aX?>jF}Sy9{iXXXxe9Nh z`>4#ZMTKEG>MK1GEQGOJ@;t)T{UmdCxluL$!!NUP2C$C9fmK;6H7=v<#az1qZH4@O zE&R(nyml|l3YoB;%vyCaUY_YlI;EW3G*5|Xr1Q_lLh@JBpf_f5IO^nujk1Lef=WOn zYBtkFF1y;CaIN^`(Ko@a&Dv`!aU z^UgE$CxKle%08KLgVV9OC>{wt&Um$uIcK;oY`ZV}Q(`iBJPRiCO<*v<|V7>LJx`OT2kOq52*x z;Af4MrkLOiNOIg`X1y@Cq&+chEfH-?E|YamMMF!`MLy);Agz_z8&n`+~?O3**47!SAQ z2|w{NmnhjOU>X=ysHxlIQ)HjwFuil(06V{Xg71r!L#bADkfHDxiLDBL$Iz z9dU;q9??2Uq!shJsk2I~?=&nQ%YsEB-{89M-f9u3n`+-cy31BkWw@%AxcldAYOY`s zHB=$@m}=BN3YDFYot3=arCWVijcJ7WlVsWG{mf>`J`>hXE;7hDeu)e+y@u3|l2+;s zg4_-A1PV%BCBu09A6G}w4Lloq$!1feY4SxGtsoC;6!Yw@BvG;6(O49RZ1+jYu?SWx z=7ps6DL;-Wpo@83@eBBKaWxgKjZHo`lhcPPc)or`T&*IusR?6^Db=&4+V*eBCiF`V zl{Eta^gsUfgOi`v%ljU{3CvS|fwi;aR=VibEGsI+SCQTopH>yCAIKvl9eZT1wN>KR zx0SpK+t?PrHeFyPE+O;DMnOW9hoEo&Mn3Y#Q?4_?%6a!5Jz3~eBg)cL<>nu3$S59D z)pXQ(=fxoA`jv4M9oElKgxMjatevQk->}-UR~*DgvF08n!?VbqBYL37HATm$*{X~G z$oi{&&rohc0>oZX9}N>KalWv_6i~AnfMdParFFVyReffhbljKy6FI7@_!V&TI9&in zO9pjH=;3Iae3MP=@#a~1Fd#E)t3Q>kjtO#=ZF}knUYM*qtaP3*{7H_ccw`)pIr7K; z|B0aiTyl`{q5%P2{$mvX7gXi{g|z%@VE%WmIdd!9e<7;>Uziq=|IvyP|GDr#l;{5? zwuqV8npqgR{2yvdOD433+HuF&uEQ)@(y^%`k#J(Xm{d9$7)UVrRe&WEu@n%w)({gn zv?v9u5sMLOk4wj5rFwG>%xWjPvUHUeYyN7<3r2NYl%1XT?X_`B&C6=lwcT~0vG>m{ zx5aEWcZM{@-m~Y!jC=3(*U!%1&Dnvp@B8H@F*U}MvIQM4o)n#btYW^Qi6*NjJpQ>3 z2ge5x%H5-*-Sz|B3YXB?hr=kVCp)i*sc(LGvt4P?b@z@dZ@m{^1%s0#NWB+P`v+1# zpO7R%=XJ&QtFoRCmO-e$fQ5*=a<(kM7lreTg;VlX=3pWnZl#&Fd@0xxv7 zzrQ;P{t)H9ym()_b#D0v`5ESH5-MGIElarKe)&c!Ud~gM=T99!zyEE9_YBGfu3J2T z>2#+G3KT0N>X%6JHX6r# zdZNwZ56ZnZFe{lmMeGbD=tC@0u--o3xUPKc^yzU;5QtA$FR8G3_z0XdfLmu`M(H7x zkhy&TOZl!BUF>?#D4!4<<`Iv|KO!f{qimO(Ur{&7(2Ck3k5q(i5BwH71l!(N=XP!) z@=og0MNVG1xTn4heQ%=;h+*IoQ?jqxy?(HF1Lym!#@qNURA`u1x&E;5j1%5gVL!E`-QaS{IGi~?+WRj5Np(NJ7Z6XY` z7g^h=Mx7wf)V0>gYl(m7<_d>w->UnpsSD@7?CfncS(!Wz-VvR3%Us%ScEa?`x2IX8 zW8gYZB|Exbu`=tG(3V>t5blo}s&a9hHEAbR0#`r0>IL}niH|+>)BMR_WKz@*>iuP+ zWw!AW>eYyA(n~EDF7XU!SJa+uH9={n86QgSvb?_T8OZ=mQTWP2>x2 zk}Bz_?$$M(PWmcYS`G1azU>7Y**aK8rE904MO3%bQD0CP#PsbD{<-Zke8-Jit#VB8 zPf!`6_Bp+jB6Ta)TNnb#ZCl6S6D#(8?#e$UuCHMr_O^)KKgpxJvlb1G8Ld#-rO(X@ zrBmhVr8CPHNZ5_zXxc1x#qumI&ic5Nn)4}*;~3m)DW(Zgt0ZxH2nmcyY42vrL=+Ro zEYIUlUQi$0X*4`T$9NWUJU?ee#| ziosSC&i2sNup_1@cK7D{e+&LrUlHhEWy-fY(nf7ZsjzX|dDC00H`rR6uXjE?u}*%D zwc;HOF%HCxABQ(?H1Dwr8>`B>2o1$YU77dXAz9R2WpA<9=_$1_v!PY!vnM@KkB|V` z4DW0^3)^k|vG}km=YcS_DAqsHnI9bS z!9T-0q?xbCdCl3dA}lI#T6CB2AjPbc>mj?4x{dDEung;T>$jmazZNTn;mEui5SaVz zcEoXCObBPlC4XDUXP<7!8(9HYcViPVF6<`2o6z*gUGd#|*~g_dA(4jq(+2E6K~^qW zGq7IyZNKV$;5gzyf6=Byxme@T8AK*Ccq?bo;u0CGibI^$WFAbWa5bB`l*}eFF?)av z;DWs(O$MOE*twuD0%U|c;UO@Uc{-@Fw$y~S)uNZ$v%ShAz0OT6nH5=uG*#1R02J@- zzjB>7iDybrw)*3j^*8};%ZGA*#8>yzHYvIDh|ZZsg&K9vG-JkfK)0bI2aEDAWs#FIx-vCNaqlP=cuO;4#8^JcS1W@FgjT*uh#`wK)GD!_k~ z2^Ke*ZEo{@I#H7js8Sa7@5V4I0&YsdT^QMyeA6fXnh*#8ZJ9v51hEMr4wNAt);>dR zN5jtMJfSD^Sp$}bI*WW>Webi18zVm_>862Ha@g~E9Mf+4->42NT#aoZ6=K9al*0i6 zAc(1Bk3?~KzhVECXoFOPCY2w%jI8#KLaR8pel-&y5Hj%T$+q5&y zD%-d%XQoO3qjl~{w|OP647}rtZIe(-fC#Y{BC!`Ch=5SIfE=5c$3M$}V36TXM_N@Z zjIxpLjJAv`OsiT^C#NK@91--2IHgUzf|cr2vlrx+zp|J*=x^yYDs`!3;;UrZL@Grt z%SfpLl?0g-l91Ez<>-gd&=}QfX$2HvpHX;8CgZvd!{PsMVqrkEExg zgxjWBEFg52VYz)^Bqh>RV8Pq%b(1n0V*vQ&~dGWtPEi<(?XlsNNBI7%h@ zk|i-;uUhhwwX$$pn$X<5m$}jhAr=4Kp2v=&A$) z(u^VvKyucD&O*2>soHZo8<}~M=yU-ZGp6ZdIaroshh;uYLwvR|@=DYQ)S8M8QR)() zVUUQXfJC1k4cnHsKI)oN<2U~?o;c<%pysJ{!J1oMY>8D$Cpw1JU{H6+j%PU$6 zeQOpFCM7z#(@-Iq9V(*?O`|S#8Ae$77ETZQo-M>_$(}9nsZJ$E+JfzZjPo#$4%tS9 zbhYFW4^oGGv#_m(cABsOG`Bhfulix>@Ujo@1yeci+2Iqk>znI5WrN`6@>W@VlA5CF z5GtWcO3*j@?6|K(GV37fD>(n4jRbcQheEJ(^tuhaa|rviZh$}fT^)oU=3N`{G(+%u z43j%)Hw?usf)0t>Ah>TSQyP|$feBDu0BsLFm4o$NN%cnKQiN>45>p3tMYOdclh37r3u*Sxc93%DHUL zJ^8UO>6iR83!#QbZ4-e;U+|u$ zlU5JJLfv8gJ3B(v?q0nYXxK_td(tO@dt#fT<-h3o3EsmU>6<~>t9JK9(*3siI2h|z z^ozOQUYJV$;LE!}&+bA!kmZO#t_Vtmi+WUsznT^RS;;`lDG@^3c0$tDYh$50np5g6 zIBIrQ;V0<@cX|#1jV19zsWQ~9E|V=2YQ|If09^|8wZ;jnsRA!uhI|+xc5J1PqWofI zB|{moi(J?)l)09zloRcONMLo4a;x7oplaOtG(hb5N~}QY&{UN9)Cix z;c8~#Ct3fCv~P+LCEB*#ZQFMDZriqP+qP}nwr$(iZfmz~>-D+kChxrSagp~?m8zt& zDp{#HYOb;793%f`Q!lszY)Ol68cYc&5A~M?K^`hl1%jO9uV!pHN`Phxf3dAx@FgHL z`0@yNYC6F^_k5HyL_TfE-(Bk)G(bv}dF#KHVap! z2qzyjt%5rs0LyUs+^gChk*=~4IEdN9YwtYCmu z@{kV0q?VGblIrY3{-p>nC8)O!Q^Kvc4p#EXH0lD=R#OPN!LMQR-WFayr^n!n&xX5C z&B!>F6m|5Wt#HHJt+Sfbv(!*Z+i0K-S0&i@23=?K>rZrF2ZS0c&^anTjz}3#sGK)I zeh0U|Ts0Q25?R!&DA}XrZUeob@t0pp%p4UfW3X8;$|()LH-eZh-~u;a2b$P3z$pN} zH%2@MXjLeDxu{wBWsrR}v}i;v!lV>lzQGU~Gz0cN3)Hx2)W(IxWb*`SK$BvUu_wcf z^%Lc-ZKH0n=qO9egvAAPRCaK^?I`^d`Ul?V>i#~4Wdr6rO_Vp>h)zyheQmU1<5G!AsY(5Y2<>d zIG6WXZ=yIqIfa;ynvvS1lq)XHm8yW z6pJ_}3r{fFT4?j5$0DCrM2{A}S(TG^NROJ39Ga6gQ0@(+Wug=DzrH4(Xa6)bP#QEIRfqre zYliw?sgZvd6yp8g5gGr~7Wy|9Lj%G~Y5DPMCR>~|oeL4Mp$=pKw*j6H*f`Njj1B@5 z5L{z0PAVaOT#6&TPN~AVyk))P4@iW{hCoEyui#yoEk3H`0+sn1i>??Yi9GA{szOa4~4WljG6vOC}>{)U$*h_7H zQ_gWKV5bsv-z}vI?dA(<2~wm}868?k#H|RqH>|bOh<6ZAR%+`g9*8Fz_;r;`C}!~a z5Xb@*(`ZLPW3Y%tOc75c=nhwrXGZ5?lY>q&2^OJK4AYT^NXAnRgOiI$#FGvGCKr*U z7x^g#iApr+LPjbYOEoA!22ntkkS7+dM@}sI9dFQptXPyrF^r)gmH1bRykx^HB>G2d z=%q{N+bebB`T;?r0;}7aWI6`cM|0(frhRF?tz+<<-2CA!i)7lPG{NKAqI49UE;4R; z*RHeM(voA#N40Z9egB5Fg@Y5kDIkxzjVt_%hx&yke_`-j%GqsPZo z{=nLmBa4GHlD?RB>FBkEBaO3jn@W3jF>dV2^-X6NuE=yv&r_MoWnp$!eUvMcWobz5NL;^jD9#P{a3ux%Nq3te-#3n zH#ZK9e3ofq8(SK!lNW!=op_NLD!QX#pA|x9Nv=(bmv_z#Msp{WMyP0HBl~LxPF-4V z?&@gEvg!7at^aOMUu65lCt`4qBxq*!k7XzD-ZmM!{)(-I&;*wJ64D7_s-ZsC`#Z)Y zNcerQ`bkJ(V21+6ME~G7?1QVYZ?>{!QP}97qarH$?e6BY&FoSG7t?&CDxU3d>!^_D zPe*SPgdSvc0k0~0*x_PIWQ$C?cc1#^74t|sGZ)q(TuF0?)<{oe6e$#9#V?@mr3=iF z`BR*Cml1!ik|)Kng;5p#h>)iE%byUr#S`=ImRtJ=0{jho7K2{aBIB90;f{@^01o5HE}lVsErY1xc|&Lm4L;? zs~cD5w1M)aylZ*S(GLHeBdRX4s_{s+t88=ienIK-p((5hcpHtG)s^j2WG=N`qhb|^ zS|)eg)ee7CtwB>#V7d{y`S-nt`zJTRevwJ{j(LwmUJ}M4mCfVZNB;Wo$S$O5VWqSY z>zkBGV><*zS2h(MK@=pAd(Xx>xM0p~et)wRLzF`E$|B-f!yLNV+4q}EN|7Vmjo>v` z*~I>y+AdOq;E>CDInBZ1kwpCLCWbxJA@QVwFqO(EVU7ZGI{c3bG2X*7|Nnc0rHIbf_wnV*Ro|hls^_q zG0B+9V)EfGSko(ZI6irMv~4-gx`mGR-62>vL@=;b2TmkS0g_xe*_W4HuI}L***sfu zyAb?rjhlPp``AA^HgcZNZ(V+0$Kg%K%+3+~?$ZE^cSiK9=$pm);p&TkRV!tXs%C0r zvQVd7+cuJFw9$#|wOhxBb~c&mE30RA?sr|&q^PXSm^M?Ihe)iJO47`jJ`s;zJZ4Ih zG6lj;Jt5U63nzdTi)Qp3%$t$#OclqV8UwXWN+nN|iwv7HIeAdb8Zwkj9byrYr<#~P zBSSZmo1$!*O|!@LZTG7pwYDZYje)5(3?UfrD_BVC5PT&xUi~| z$Xml(uTx7e-S`FryCmGiB0}p02W8AxgLN%eqq#9V>Qx7c+>{0l-LwXg7Ous)iuLhw zmT$0gR_b)mp7#Ah0vGJyX)V}@iD36!F=3&$^*?k55z?Q*b^hEy>MY-Yb(QSFbd~ST zz5rmoL^Em+EG0%=K6GyCGhkpcJt@hk3(TJuF?Br?E=Sxa^s!WHAfxs!_K4f?{pP9G z&w9$f+UrMjt$4;4FI>P{^XgShh{YdGS(NgU$+^wCXi05o>uIX#$#1HuabnNa7}+Rj z2u?nH%tB!XS@GB;$1u{e25jAL($i7Bt~NZ~y%;^5Z*iU@RB*vb_7CMeI)0 zbx`-?_~@At@hvX%_o)$LXO|s5(-Vu}thAV{OjS~1Nm5aDG_jZGO%d=ZKb!=*>3q^_&TTopNu7(OrvDOG{_M)|Ji#@cRf2`3eL@?THTs`O;q) z(vp9kNNhE5C}5Mj4#i0Q8Q+}jSy^rR(2UrAZM@Kw{+d~A|APSOoF02e-;-t#*vc={ zQ;P9s9yE4_8&9X{{^IVz+N9)x3{TT^K9}QVeBv?-F%4VtMfHsg`69$vG>)%8$32=S z+{B5FjG&~EWn%XLj1Y7O-kI*0UmkRW$n;A*ssGfU4mFxW>OQ1n4nyL>uXIyht3Nbq z)cNGl4ZJW!oP9w%d9wKmN8>NodlE?3o>TlO22H!m$T)lz#;e@`tAr(Z9#Ap~{gR-Y z({|7J>G#c2o29a8ruZOezD%Zr)LgQF4$hNFGK44)++%u^Y2kI^qsv`2pqZFgL_Fds zEWw(FX{{tvp5^^(3WHsvAr>9^J8&E|37wO>{(#+38dNvB!LK%YzB|QdaVsMM*ziye znp|Q%IB9W;(}&UqV@ozg!XbN4bY(?)(BJSYx$uKq=;-V(du3RDw1k_Iptekg`VG)E zEYa%*K^a%|R58a+5Qtdzc8*R)7r&DT&1_qGKA-K@q6tVi2%nct@h& zCZu6Uya{anau^;q=w0^1W7%B!j~KVZv|{g{X*x?2ZH|2V1Z+rKo=<~}F|Z8w0lsbp z@`E&iJ!K=@zHwP))tB!58a?l)lQjyi4g*OVPj(er4&smh;zC}|b-lY{0i8AGs$cqI zV@NeMx)gru0S{6gBwWLdGUBWqBK0=t(ZR4xv_skG>uCm$&pI3UEHj*it07;9>nv;S zHWMXiCAJv`r{@X93bh&{CW;98lcrq+)(Txo^E-(L%*|@##^&Ja{A|P835TF^GK{kA z8+PsMnQ_l!i{`}g9hi3lfN|obc$#ZQL$eEtsFhIkBUjDpQY0sf2vqDAz?+rvsmxfr zLg0EndcOJdJQ7$RDxu9>%9@`ZPn67`>5R%`%Vw2jLDWN=b)-S`q?W)@wuprWlzAOB z8*>Y>;K%*E*<|z^U>!PFJ%FuY5#^CC99Qy@XG0iiXQR#o5r# zzT+mjzuv9{mLVt{IsMPfocY8k3g;qz2JTNYn~vF{E%hBlhY)ig^5bQ@NQfB->Rdmn(V z;rv%vJmKp*f*V}h=jZr9I5oeoe*HIuk$pqt{oE`l?}+pl=*Dn4aY(4~0F4F%2$H`7 z4TdDedEuaX%rFDfNx>WBSefKt8)kGrB;ZAfzFR~B6)1doLYti(kV1WQDeIa>2#jD5`W_4Qi&jB%s_3uFa?NoHs`fytp%Gkybz3Kx zd6rQWPe}j780*NBUJ=Z5(NR$pR|v$XPRu#26Let=KhCI^&t4!IJ*Vr~3VKCskExTv zDnSytZaK7Z%l4SFl)SdNO{|5sD%7JMH4{qHE-_?>c#EL4d|C-I$id7na>&%eo@o!b zZBpjX^ICESqlnCr6AxQ%h$4Bknmlh5|LloS$sK5R{EJ}D0tGCzNKE0q_9|f%uc{zD@#PA{=UH+ORR78WR#q1b!Ru|Uq zFk5&0sK}%6c=-Zfk0k!-_UDe^e;}SI@JK28o=KP;OvU`dHNgzQc%qSZx8@{ombz zy$d_H2;Gshr`}%TczLlr#KL&;kls?}53(u)vSz=}=YPLNjV$)6LwpZryntBlq*giW zHm2Yhf_%`Pqn@MSkg+}{h>|Q|8XIy;7<7x5;5GVe6*IjBWAbTJB;zWaHYpm<7gA*A z$g?}i!IY#f!M_D!irALzJ)}$&!>OP1D7ZhQV+!k*%`a7slnF7bkW}7MC`-$!Q|Q8| zBDK3DP{@&-K{KmQ%PB5dF)L3iFDjRFXilp#Do1A&pDklM6RdD7v0G5LYSqlZYU)M%p;mopy?8^ut-Z2B)J>x-MhgzO)V9&Yll^#49+UhmpSf-fLXj4Jqi-q!lk0Hk8cdpRg zHC?{Ds_ke=pJg{X-u{FZ17LhQlox!ZtM3$?{G6iD)74$Rr>edW6qc7akSMPcpxNzW zGi%7SPD>W7pqEI8#AXm#B19XjGUE&eh~X!vsOcA*l!&AkUxlY@JyNr%-p{fTOdJ~k z7MD3Wrm9VhZ@+XdhYrqORQrEUqaveueo;q)7!@&ruJlYZ--^q_X(crMT+4ot&4|kr z{=yXqlFCRl3e#Tbt$D)D1GQQ&F&fe5NmwhCv^IYWUtu4uKLlRugkS5Woejur!?rBA zbCh=a7ta{5m9Xr@v}074P}nB0_Giy1KD4!?WtW*>6tpE(oN?CJ*2EN9=jEIkqvcu^ z=*e&o&s-{$UFRH}(X@(@xbOcnv4xjN$w`QaK2Z^mfd84YQCT5+#dhECM^@yrR=I-h5sWjODW&TIsqWC% zGw-cZ5YXCPYd8N>0xXHgCE43(&z37iqCKo%llJnxoolw`f$!ertB7I?yL7x%Fn`Yn zm(B*X*$gQYqK@GmKRNy1g3`Yu)qNwJ9^F?zz2t5@g*g84rjjzr8>DnZ%qZI)7E5 zrAK91%cEP>6Racz$)^}a)feUU73en@|1wcipkGM{aZicbB}>zaq6bP4PbY;DA@C{o ztw2g#h>zO>GU&XpN*$43nQgJ|zZOQot1J=q!mEdlysM|cKK6EMmjAuw~xc^FXSgiiDA0l`+42Z z?cn}AdomBQKNBPD0#g0oa;gPc$G`a8;iUzds$Y7*3tent?>AOXGAQx5w6TE?*g3H= zKRfYEys!&jYCaiwr={y{Vqj85>__?WRAPf2_BO;z1$Fxb>~4A_`ykqqgZywK4qpLc z$GLc++x%VVMW*wysM*_NHcG+*+VF|nwnQg55h$-vL`1zbu-$?$+Dslu9rilJOMlZI zbRzOx*;4$JMQg;ou{}a>-_avAksz9hNxKgb9%6<#5G;ub-ORB)L~k6JJc75>kRh6> zNV}yG9(4LE=TEco!q?zKxAAT?pgqup1gfL>ubr`@^PWMsnfTGt5DZuN*#RmdwU9(t zcQ6LObqw+*7V&P$a1mm@kJWs&G_l9sm67kO?|d)Q_baDw9f+pZY6lNacJc+tjVk0C zt3sy#hM3$20hBy{GbWBOek2B8vc*oU88?iw*9B~n9{ZEh6*w_Z4jG1Oj1)HsFQKA!t7Z*DIPIhSdoZtMkxpat!~b#9U{!fi5Lurrt$N2zztdN?o4dXf<1p{I#%g zPxO9`2d%jpDqYKYNo(g}Rl1J5D!wuWXY+@n<4)q0es#tU<0S1f4yUe*R=O{r?O?st z)il%)D%HjvDoBVzG55VFqb~C$+N<8z`zwak~@7PW_&lu{BKf|@I138+X$)UQZ``jRSl-8*p z$2t)&&V?M}Cc~$;i_c!yis(7TZZj6QL&Uc9ZC;*E)3o7UvGf$WVQ)wo2FN!y%4(6W@I-QnAR~zhLh;{4bMNbpa(W{_T z2B~po$-s6X{o~z?+L0ao%&WHzKe~A{+Qq)P77P9S4xgb*^}3a#sZE>I)2q6vLy-)8 zr#-8b8|%dIOy8)J>JrS+!hHaCW%i}yLziuOu1}D z%&^9bAA{gNEeuT5)X^Jn)DPk%V9O>pREiGbWedF@ByX20(3T7@GmBkFmiUP?kJRP! zFb7L8F$GLSmga$^IBI%PLPu*zIP?+jT;afQk5ym@^H6j6B~r^a!3_V7v0ghDvyJ~n zEP5v@b$*)}O(?=SBC>x%)C~X#i{yZ;`Z0n8VAOa^oZK-G0#QRIP7qM}g+TOez&D7- z(OR?T!6~u^ikQvVAv1-HQ9S-zvJQKH5l=7a?vOiB9y2{oer6>~!ZaqsWAtN88gCQa zMbOR%6h~>Mr96tkm!c{Qf^uI1B{ZFu(%9x-<2gB5 zTM=6rIpn9}j0#E;oFE)eK0L!XK8jzM2;JCqt?e9GfuKOaxHw}C6x77DY)yR#Z*OM~ zix#n(LAhjt!nXSX&+WYWnSAzvX%g6Ez^z zz2j07q}h88?cJRBaW>Z#19WrB`CDpqacE@6Ox|h8;xdc-EYcPK%7RN<{f48}w4GMI zbp~pCGuTW`UFJfL=@@ld%juShm}oCunMl!8fuE&U;!RK}pdrwKtN3F%}4BpO?sOe6IPb8`kgyy3Jl8ymV{l?@!YpP=z zE~40Ub*Ptqgxjvzw$Ym28tp||6`IvuX8tVd&utq-H%lA@&*(K|S+jCl9CSlDrW`J` zv4@2Z7BPkpHj6q+f4nVRc;g1fa1|U=-cv{{!@LG3&@PY$hsy%Nr=iZKMq7(cymzc5 z8!t1Z%~`w~38p~@QHC!ZY&9#>HDUDAv~9$%gdXO`^HMIushgF57Pq1lmL=0$a`{`w zJLN&lu7j)Hlg~>a*zRs^ zD^Bd}m-_nsbxXlCH5}e6@Jgmk_Do8StX_5yg1?jHM4N*ge=}%7&VqAOka&d%homzm zGgG^9@rFPx7;|V?8q1lx1?n^aa=SWl^uRXI5bawM&C)$Gy)nF*RLb_@CdLndH?yB+ z38k+q&;b-OWaWTGmG8PL*;Txqs2Ncsr(+7~h{3;F8y&px-85b77p+dq;bQ^oaB8y|Gdd8Wt043~XRvO`-H~*ART`0{k5av^HNBG`+C_PmJlW^P)N`8JHrK8x%35zb2o07hlo}ANI0;RB|h30S{^tetTg6Px8r7T1IQ&7=Zh8JW#LJ-ToA0Kv(DJ3 zm9O~!nC&UyjeU%(XaHO}&{8SlRL47DaZ_O&ZB9p+ zwE&3jKX;R^01e3@e4a)+@D+ZmpZYw3)GBrn4`KbMf`@>EJo3a3R0u)+S5U$6Kgt6B z1u6t>ZJdnVoc@^sG^?B2X{w@p)u2+Q7*D!nt%_e*IV%V^R5D3#E>JsTb2?jaj<_VU znO9OQL{_V%L=l&YOXM4016q@7vSLbXI6>?Nt@?)mGV*aa?Ser=E2in=r0s!$gkY5L zl^k+Db4$)jqGswd@I3dtUvVG5&pg$T+IHQawE|f6S?W~vYY*T2jSCwjj0tPg9C{bt zB>D?P$aB))trPPc{t}0VHzF$J)s*(vu;Iy`mdF2Hm<#_eKLXr%itNONB7TIG#8LT=*d?BoR$)J+?h?AU`ig6w!^Dfdf302u?~G8mIc#5*hM4u`<-D(Fzrm!kD>4+ePQMBF>+;`14F^}uT+M`K~H`{-&2eO1!mLn-r54Ko+yBhSPYlLfUr;vF{8~qmpc6!~3 z(N#H9Cn2!Z;A%O;!4AV603sE;v{-p7N%Zly^lEDVbY})pr=UX#bK~tlg7k*OsbZxK zxLkwBt9b}m>lo58cZb>E$6^Q}MD}9&YMo`A8CXhYU3t@&S=NjSBBU9Ti`bQ8?D+1e zT@Qk|*v8@Jk(YymB3uaBOqbECc5a5_PJiaq3+rrY@{TfpldKnDcn05H$QcxAs=7r6 z1vOtogX@3$#aq&El%SMU^l@m4q(PR==4SWU%j+HS$d|*)SogT23^y%XJEvO$-kYeX z_*s?QC=I>p&>pQ<>9`zfO^0U-wYW-v6^*XmeUcB%alS8bizJ=h=__rDS1`KN0TqjSOIE^KrGNz4#aPj)*W3AMREpeMxWGGmp+z=GSUa?VN9K+_tnA=@>GrD;sLg8hSKg?;Qg7P3IfH;Ngfr^ z+7nb8;s8crnHd!{*q6Yo8GOX`S_?hC>4KvTVuPSMmjfYfWwPk5?HiP3Cr5y{}>P>W1mR}U9^Hr*(-oBz=dF7b8W%j8^> z{R}3lEKfa4{-d<%QA2mzdP|dr)AVP8^jWH+yva60tEx9iXW-h+R@YHh*Jw25vYOf( z(vr3vB>gzU(<9?u-P8KA8b^~zdpf%sx@HZj2(M|%syx`iHtTW5?(4H9R;n|d+p74| zpyX3vRLiS7u@~*Y6-F+IqTGE(YXRh8Okss+!w+{2_yPToA&e=N8Qsdu12MLpqZS$$ z42sge9dK&&AchrsDCH^}tm_}3L6*yF&k>ut_fI8fmWo!gte0D1qVTqQa$GY=qsl~5oin}twRg;VvEjVj~ zk4eV99X5@cLCT@y@;cMEajnF^VxELdm3jSlJz#dTwAueYbI$72&F^K%!}|sqTRXbe z&|a>oJs@Ah;{4ix3Ud@wJAWPGMW|KrT&IegT`V2H07xx@;LUe+q=aX-L`PHEqa_%d zc!_dupjF1|@C>lbvFtG~*>jyHWRe>t)l)_5x^N0!HKomA$oa;1q&YhMaIVc}@AvHu zm>{A;MiX)KjiN{P_&GR5O;*vNQ6qK(~{Wk$OLXB!qc{F;7%Eo2^zOSx6ktv~*P_hTFT9UqiviX3rg^06tRxu6d zT$2etG1&+1J-+dWH;6zF4Ban~k>_kzyRN=?yiXvrqjz40ZnPd^lhAhZAQKiT$p`y$ zlf6A$dwA+W&odHjeK#S1kuWYHl0o_H#;3yJEcr=y%81;81-0=fuIN?Q4cDz2(?nHVCC`rmCh80w!TyV8h zq)r#wi&(R|Cs(a<0loF7AKAG%zN_#ccVU1mLNsg=6{UG=-ULjMvwXfyBGm%b6{2F*{T|JzSjv4NCF3b@Hf)wz%UZYs z3xH1z&M=ylnTt?HWOQ2DFjUc?G98%4_4-xy_p6<@Ykt7#Kg_#Y9_F61{>!olm69sr z6zEcUWu1Wa0L5~UhqLt8gq+~b;F|)z5UHY3br0R$Ta-o7!?Jvy%E0h-No2bxtWS2o zgZII;B-HbU3JgyD^B)MR#w=IwvX3i>rKZ^>)n;SvjwNm*zZ-c_amBd>OuNEhyfb5FS zpp|cvwj9yRIQ+MO-!z#nu?nq>GOqBtLnf6I4J6;n`PRv-jdG5R3_gI5i?)NsjV2#u z+s-$=|KP6({<`feK>qqwgYmDK>;Ja5)7HUS-|2tV+xg$+!v7pmNZ(0c@*gq3ky#pZ>5oy|Z#mNFX{jaJ zvMf3$wNcXmFE*AmU&}W)Kpz;Dv&f8N8LV?c_W{^>Me2j5o@MVyU?m~)41wb~=H9rc zIdHkltUJbGtTY96BIBmK zSq+Sl*=Vo2B3}BVPiCsdI8!w+mhtCD7CgMlhuwUdmW_O)r)(DUEs1ZJ>6y(Gc-;^s zbA^Lq_q@Io+CL-793v2FO`M3eK-Pp!F|}dQ!$fH|-Qhy!ElWKSflb{Xt-GV}nX-Nb z21gsB8p@Cmhl}0m$il>h$*lF4W$%k{?qur zhf5|}5i%A7G47&4)ue!e{mx+%J*{}^%1uVpr`xj(in1)N5d)-7jl!*4D<2p@;zrjj z^OzqucQz|JlI=(y8vkg?VF@$Q9K0oUThe@Wx{!VfSde)4tN77p%JLNmBcreqs}!{ zEG~+lmJ6a67Ff|6oVBKW$w6157u*^ME`{`5gszzD$--N3+%z5pc*oxjEN6X2+0D1IE~XBqa#^u##2mZ0 zsk9CYf(!EZDZPcEKFJY%jkpw!PIF~BHEu6ZZr}UO4u4-DuYjhoT!80_zi&of_IY2X z#I7_jugnQj643<|W2_=aIrIl{vFskiN-{a{((C7Kiz~t#^M`foH6hmse{xp%jjHsH zP3qEhz=x|HDnGILM7uNT1#Nm=ZVz5b0Iah_5P1$wEqw!jArpZQUi9D+n?Q8o^Zd(! zNwg_?p)}^R#06F+vjpQ)%KQr#vy5Ef&qLV}{3Ox1uQ>M1Jwx%i-i{j;#N!Mo&9sV( z@x~%e^Q}>5%neM79DROpi{Sbp6Q=UB-?s8oMH)SS!<1WW0Y-R zYN9)dh#RpYd?;3ZNm--)7@>*x$*A+51sqnJ=N_#ksxIZ%DpqsObYji)7xem#^x8Ju zve&5OD}r9lz|D5PX(uVm(s=x>iXoS@!v^$p{WuNNOD-`caAF!3MRhjRY?+R5$ahRB z)(aUqfWjynVmuQS$Utu0YXa?Yi}Un32bu{K^Hb7|=gblso3nWStXqDqB@0p{5HF7r zQ>&jRo$4Y5zAd=$5-;xxT-4&QBL#-8&j^x-d(~k$jEn`59#4!^^PT(CVCNi zu_S&k-2J(*J7bmN~uR$x)@w4hP~0Xce`*-V-@Ez ztQ+5xO?>k=M=4!WX-vl$p_+x|Je2F zIJNTe0{iug67gTNhJSB+VE=FUu7Cf>OY1usni>D+FFspYTX91KnVZsg%q1v)h;LkU zqY0)t*$V46&4@h=jh_DFnfQp0H(TtRVO+{~;gFrj(R%S_*Gc%2$cZRkZ}^J>kpR>k-Gel+(N>5Vt&=L=zUD;;$S?JyHjiKjsZiXwgVd)lNPF5J)q zonTKw9d7vxMIdgHvJ%6|a6p7GN-T!Qiq>q+T8z5x9k&e&I_Sj45nPFWr^xu)+9S|2 zvf%9c+tm^2^UH|$A}dDHvU-TsO8Y@(&TO9qVB99=)t~D#gJUpQIb~DaKuF2|j z@2y&8WATDIc{Zna{scU22UZp_`39`+s&$p)b$GiDnXKyJ)GhRVcwSLau~M0oz)lvv`oPICCdOY0H!e$<4@J@HKc)K(#4~BiX^K z>b}pRo3YI?)G?~)@Jdou;Ba1UjC~iYLpECvf?1s&OPEFsol)|6i6Z0gA$SoJo7umdJ(nEPC_8Z!Lwrej_sh`E3cXKTMk$-xvw!bf z;B9Z38=}L1!(8i$4+s4!f*efqbiYXtJ;ICNzlQ}341ILp&(_Z+Si21-E-8h zrqent%H4W?<(_4HCgKtTtSI)GuqSUA=*}=pkr|>$e)4r9d>oTUL0xHW7w$IAYUD?! zae36bh@j0(;GwK;lGD=qosvtFT;T{!WO|;JXEh%)Ur^momw|>hu-0ktHr+b02C#%r zkZpFgR4HE}=Y*4c{i^uwk%&^!Cc^5&`$>~97VbhwNyZ^jv!vvJ1FLF5-LBv|UqL8C z;)@1-J-;|BHwF}SLfiL%W&QhYn#JI4ndR>rUsrSxPnH=`IUL!BoNsY96;U46e8xElie>hqx>am%0=iiNkZw2`7@GD@0izf^&5Z5Rup~&IDinXkJmSCn$D<1TBT0ta#uiJwq|QBoV-gUwF|Nh1`LIPXZ2ZN9 z9w03?KNqLJ{N>-I+0`Pz4|||3CVO+t@if@f#W%J387r5Gy(v{pSKhWyeub82O8ZH-l;Ou8>+;A`eYiN>L)H zLJZ2x7X*k}IAA$I&c?>(H27e9Y8o^WZX$~I?kI|feLs-vK96y3W=zW4Xv9~1S)}oF&ev4Y-=8pa5qn5iu z9&e-mg(Ono{U+CGDvrN3W6g1vwUVTaT|b5L$!X55btQu>NR3A5Oq!OoSErH<=~m+iFWS2i}D#GDXzy zR~%WT5k_ep?V5Ft!;R>QeR

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 080/145] 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 081/145] 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 082/145] 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 083/145] 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 084/145] 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 085/145] 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 086/145] 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 087/145] 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 088/145] 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 089/145] 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 090/145] 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 091/145] 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 092/145] 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 093/145] 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 094/145] 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 095/145] 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 096/145] 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 097/145] 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 098/145] 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 099/145] 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 100/145] 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 101/145] 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 102/145] 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 103/145] 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 104/145] 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 105/145] 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 106/145] 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 107/145] 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 108/145] 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 109/145] 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 110/145] 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 111/145] 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 112/145] 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 113/145] 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 114/145] 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 115/145] 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 116/145] 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 117/145] 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 118/145] 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 119/145] 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 120/145] 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 121/145] 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 122/145] 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 123/145] 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 124/145] 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 125/145] 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 126/145] 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 127/145] 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 128/145] 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 129/145] 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 130/145] 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 131/145] 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 132/145] 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 133/145] 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 134/145] 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 135/145] 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 136/145] 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 137/145] 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 138/145] 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 139/145] 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 140/145] 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 141/145] 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 142/145] 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 143/145] 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 144/145] 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 145/145] 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. + *