From 3704c2205b435788573303a0698a082734ae76c4 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 29 Dec 2017 16:14:11 -0800 Subject: [PATCH 01/71] 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 02/71] 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 03/71] Stolstov/update jackson (#158) * fixed some errors in javadoc and updated jol to 0.9 * updated jackson to 2.9.4 * remove jars from DepFiles --- DepFiles/public/jackson-core-2.6.2.jar | Bin 258824 -> 0 bytes DepFiles/unittest/junit-4.12.jar | Bin 314932 -> 0 bytes build.xml | 8 +- pom.xml | 8 +- .../esri/core/geometry/CombineOperator.java | 3 +- .../java/com/esri/core/geometry/Envelope.java | 46 +++--- .../com/esri/core/geometry/Envelope1D.java | 4 +- .../com/esri/core/geometry/Envelope2D.java | 42 ++++- .../java/com/esri/core/geometry/Geometry.java | 99 +++++++----- .../esri/core/geometry/GeometryEngine.java | 8 +- .../com/esri/core/geometry/MapGeometry.java | 20 ++- .../geometry/OperatorDensifyByLength.java | 4 +- .../geometry/OperatorImportFromGeoJson.java | 4 +- .../core/geometry/OperatorIntersection.java | 8 +- .../esri/core/geometry/OperatorOffset.java | 4 +- .../java/com/esri/core/geometry/Point.java | 38 ++--- .../java/com/esri/core/geometry/Point2D.java | 4 +- .../java/com/esri/core/geometry/Polygon.java | 74 ++++----- .../java/com/esri/core/geometry/Polyline.java | 2 +- .../java/com/esri/core/geometry/SizeOf.java | 106 ++++++------- .../esri/core/geometry/SpatialReference.java | 2 +- .../esri/core/geometry/ogc/OGCGeometry.java | 2 +- .../core/geometry/TestEstimateMemorySize.java | 145 ++++++++---------- 23 files changed, 342 insertions(+), 289 deletions(-) delete mode 100644 DepFiles/public/jackson-core-2.6.2.jar delete mode 100644 DepFiles/unittest/junit-4.12.jar diff --git a/DepFiles/public/jackson-core-2.6.2.jar b/DepFiles/public/jackson-core-2.6.2.jar deleted file mode 100644 index a7d87f067340ac378230a8ccf8b4dfc9512a94c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258824 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%0ANnsM4|)f9WP^dq=- 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

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

SN z?n~>Z6xraY8rwcL-E@>)hI5p-G1B59|s& z&v|H;i)OfySgog=RHZVEo8)!rPlEGI3|>&x9^0Wtq{D4rUeab=#>cIjQ!Md?dc>Lf z*rH=%q3dX(w;u`xl3_X=^!W)%xeXgB|?GK|X`EtGco&eps;$0UX&aEZ0Jslr(-kIYa=1)@Z!s&ju zH*cax@K`o2;k;ya(l*ilLeXJ|(uJIeAgSVcZzVOdVRPwSrJ#RDskA-VN7HdsZjveG zaOG{H66vvG=`s{#cw&VR*UZQlP;tc3zs7n9f4qk=<@jhiGDx%Bcs^~O9ux#R_@Eb~~j31#@ zP*`j8q1JJ~2=k?(5Mr!_;Y;B;Pbz0};Y&$!bC&sTSu?ylCysHdn#(LP%r+9 z)uQAdIZSYP^A54)AH{}<`OJF=x#fZpQ+?Y}4kWzh1*9%vy^-~U99OEsiT*fu|L8)3 zMnzA2b|X+tlDwK>7IJ}V)Hx|7-qFm$L)1OhyL@dqOoRCNRsRldvIQ^=n^Iv%Fo|QQ zXg{#5WsjUWB3Kl!2vqp&}DGm5sO7vDg^Cb;aox?qye5%z#-V0O6*O-MgjPNVg!|Jh8=qIbbR zp+Js`Z;HNHzRLK586J;s8^1t)-i7xq=pj?RjpxOGm7SDzlY5?2hyQ-VAr|}2$1M8R zh*;W22Y=HWB>m(8X#LTno%tpgeEVfs`cGab^DVwn@`w6r=9{YN{2LkJG&_{%t$PsV zEqM>+Q|mVKEAvI+TNiikPfXEQ{=+HXxUo<7Mddd!^8+Z86d$P^V*sw5xQ4O1W1GSF zYmi(}1bgd*IR#F{fOt*Jv3on*afv;l@@Q|kY@K9AIA{Qd&Y_`0R$n+R$NFHng=3C5 z+CAhl#+b7MFOStO*=-CoPE!hLP-)FBSUA-Q?;tX>raFf+I!K({uh~W9b4bHEk-s~H zeT?(k=siu9E5(8%o(~Dj* zSja4PTm|E`v^r7Eodr5((SDMk@kFpZ0Eroj!D32Wtu?!;3X^EPjD-)2dhUBDEyH2jA5`TLY%bh+ zqcoIg6l*pxz7emE5DvZPw#Noqli++tYQr+W8ASjf<0E=00YUn?0MugaVS~chVAdhV z3_I9ivLQ%q+lWxj_0Bog;;RxD;UciaA9_1KKm^Ijn-E40QZ_Hb2g#}$c%73xZ*3V% z?U{8W?NfI~BGf#Z8zA5eTa1~DiWTvbI~t)x?=HzST8ruf>Sa1>mH{-$S8s~;Vki@+ zEUf!icw0Eg&n)>y^q*wg=bZ|(0ja$OX_J%+KDNia*YA4o1snIj`|6kMz_Cyau;XX8 zD22-a;H{!aL70Q=y_5of0cc8*7V5?mJsP4^^h~CEnnvhA%zB4*z^}<6?GA&C2)79E zEr@<7b^+}&AZFF0B&n<&{H-lJUcsD+W%=L4de}?F_UOg|D7u7}lQFJSL5`BL1ck&j zrBaLcS-Rb3K9q>t&GA9Bs_QF<&jOY)?Q4~Aktv!HTE^dxAy{}h=)LZiXB~s{b`+_D zpj@w`fgcBgjZNJ+p;LpX;8|b;Io_ujvQ^+z0t=ALuV=p*TyF^Wu~fbC0gp z!vs(%-tB%ICU+c+g+oIWiVBc{wbit`^yK_dOI?f5VfnnSWyxW{m65Pj8i!xhe4bk_ z$nv_;FK+}I;lb{M?7az+Vq)ayJ3zHzB@+*~L|9)s#`U1F&$b{J4tY+nRSi-NxlpoI z4I}SEDC4ksrXJc$LMWkLx%G$IUSVpe1MJh%ZH96+wGU0xr3M#YLY3PCZsM~S6ED_; zxP;~9nj9kUMeW-$AG8^&T#EB_G7^yA`=QuiW*}98G~9(O`?*MMN-KC0h+7NhkA>;}g!mk>9?5n*0nR%drZbtyhsuls z_0edSeK$#xqrO=kahZ&$w0aOx44-ZE#~>hu5Q(#-Yq}U=IhAWmQgWy|Id|*~z{LsAn&3b}qY$B>`0}G8 zpa|K*(hZmCXON}GRw9fA&&AL*@2L;B@SI;$!pb_3YpSu}5cY5PGJIITH^bmLdcRQh zozoiRkJbWOcHV+-!P-maKP0wr-MHgBG-Q6RYDf4sqM5Pdb0+8w=2kg6fdF6cBWM#F z<3vG48z|mSi1f&&aosKD zTP%s-l_U*CgoMD7#-MR3hyu!S_Z6B-!9)<$C@{LL#)g)59gUZGKcrCY4k9466_s%P z`nI>VQjbv%ZZlOm6O^s3;=hhxJ%y(dTE$gGgn?gi@WlK*B(kV)KfgRiiPARjaNSZy zwxZzyt}gY#$5JI;0gmF$5rJ^`{{Rnq`i9Fna%(`DZnS=glh;hN{l%<(V%rSy(v>$>dyyO zF5=~j03uT}#I>9i*ga!rG!s)cT0r~0GBaMIDFaJdXYJBOd4da5sZmJy1Ns{>_p}P} zrQfO~6ld#&USF4NvJj44M;S_hpiwaiP4E=h41{0=Lj=H?4_4e@-GUR819o8| z%GW~E4(Xmd=$}%`s_f}M5RUi{Qu<@fqI$%gV24sEaz?n3>L(ovy1nQ0exj$N)bwSD zd(sSW92sZWq3=r8V=kwYt?0`RgC0qLEbEg-grPt1TUr|2F+0($+MADRqe^TamJK!%UvNlVMtkn@pL+pR!eB%&!2Rf8;$ zby}ggR!D(*5S~d%<1{J84ec-mQ?$EDCBSOMAgmMkQChJ5RXd4e$Wo9EXd`C`N3lZab#;vewMkeLP2-D;8V+!INIMEgsnz5#+69_3{D4 zg`7CgEvFHInYNrpO1@_&5GBK#&Ou)8gZJ5L9nME+44EE-lkWcRGg4+(4s%$>d$NHU zMOM&9M8BE+Yj_U{+hCBMnIT3}pazd_82Sbf-2`hnaeXIddlxDxk2Jt7blpgz1w8(C zp?x}mLRciD_A`e~)Vxxj@A}sfQqW!=$j7%89*#C)cU}o+nRXzh3YVgR-Wx!3M$6B5 zB=}9~Ix`XoQQSj0vt<|f-lGPk$>q;@L(ET@0X9ECP16|SqC2Co@8K5G?Fd z7tpHX?HqhY{A(#S@KG{q8#tEg2Z9gEFtdbW^*TkOXO-`Y5EJq!<{SOZk7AMN^brd( z34$6zCKg@uUxB+=CXPpXlq22YQ%CP7CSPzGN53%5zx@3M#8UKsOV@pY-A~FnguTi+ zCNPK}=_&etLz|^-E5^LQC#P=jL*G+4CS{LmJQ>x$hO-Ugh$(5i!umIGt8hT9Z=fU0 z1Km?xBmFJJd(o<zUD8X_+*~g)>ZuX6s@N2lK-Z#n{U6-qfK+n&KE|ETi`;;4y6q^(+1x zW9Jy03DC6b-HkT3ZQHhO+j(N!wrwXH+u7K*Z97l+?fZUp&d*b)s;Bz@Ox4s(ci-2I z`j_MnajBNzWRnu(jFfPOXh!-HE~^?BRyCdm;At>J&918PZ5QbeeNBCXeNM_1=Y++i z3e-GfDnnKORDaEjURjsSW{nOBB6_4vP2e=D+bvX$lC-!+*sI%HaZ+qg5C)0JuDNqK zSPpl0UV+} zxyYxhmGj~~6=OV3=hh<1S9KcBL01yt@B_!PP#BN?zlx3BaMhpK)#ygvnI-gT;~CtX z%;X+eUZo^(>p*(e3iKPgv-3WQADePEi@G~zFz>Zo_Ev$FSp;rL^Bq~=@S{$k$7dG2 zA=DZ`3-cIp2*9uTs)$olSt4FkqhMA{L% ztk~>YPIY~mVY16(Z7W*--D;S*4QTtae{gOzqHgO|17@lbdRoyA!~qqvRa6N^IaD40 zjzj;C)}aO~(M%=kKou9`u+^=eFlMCb_#x@`qg1iq%Iktis3Yv&zI+%Q1f#}pV!oa- z#9n+c@A;o5Vsyq;=X4IOQT)(b`l+@t>E2nryACAT@<`iN+ah1a$`b&o*{joid7cb_ z2T@z!+(NkQ?_#F>RZY+fJJS-f?+iy@9~I?^>Pjy8?%4&P{fea!18YUjgVimFtOS9n zog=rU6{Q`R7y!{OM^3<*4ERrO3do7Uxxo3A@5DIBg+Ea3!ZFwJ1GqetZlN0}biwqi zaDs87BhdepDp`9azh5hEXxXK_rA9o*1!TI;mLz9eglkKHpa~JXI!m&w)#FCJWTALw z=~s)VP)Kj!T{u&3VOl^ZOgES@Ku%ir;i^FiYuK~*V-<+Ovm?rvW3TaJKRuZBxNx{EgeTh`)6fd5Z~&Ji7ohMRGu3;eOhOQJ<2iN zzk5sab6S~Xtc>xmXK3`x%V=Y65y2e~<_$Ec0KFoL4;bn49~4pj0m|iz@`i1J>L)Ta zEkBZ?Pvq(5?b6?GvE);F97DQNM13M5X(Wl}UZ%N1Yo*bM=RUa#`cA=Ag-pUX11MAO zcJFr0y)U@$8;|;9Pkwdpw{71+TfO-8@6dBWEYCmtrgtO(_=QPNjI!o@f_@juP^jO7 z?ic!?%y~)p-T`OJX@}7I3G^RfyGMY)G`qUtH#BqYorF?fBK}v*tm@CUg*ULyDi9PV zxP8fSg1irW^_vXY7uFH;n8nK34^Jr9 zthRw{@%xhuUEUYxoRh?z;(uQO&z=+r-Vc6@jtWzo%Z7MacPlr6hMYh-S8|i}oVlIr zzyeown8qLs%QC~NE!d>X>vW?Rk@_v@5?8y)=J)*m>nJR45Lj0-xz_hR9d2m!_4eFb zrXn4ZH_>yE@i%Upn$tu4nTz-D;)}{8Nh% zbS^V(eae8`%y?H-h4ik9M6M8@_VXL~>q=6=_1V!2CU&z}CC1~tH)Hut3Uu-`!uW|; z?ip%jEvM4st3GcPn(pw4J#P>x;Rk>cb%qw}Nh|3KD)Gk@>xJ#P@=EpuknDtyJxeuA ztESu54&!*iTJFIlubGQUy0wvSg(yb16akYGZ2@cNAHDkauur6J1*cIhAJ<82pK{d| z^r4Mo)BKJ{i;n(Yd~3Y)N@Pb(S@0+9oa6zmq&cK+UpCV;Z-C<7ND?ovL_05~&1aJD z=VDhBmrZ>)pAKE@>KMt!Y}#96qB!yg_=7y2yxt*A_%C!p!OCVXgKTEc9I{L;JQ-bl z#;l=Jp?m%~`+M*CJ}&1LdLmq%K<3K{hZY~w>Bib&uxC#lYkhIF9z^xk)_p54W=$a5 zrpb<=nlJfveOBE<546{%7v-z<2;kZ0HOYs6yU|VIM@P`FSvotRuU~=JYOeqm7heh4 zQT7gHr(E%1zs@E284mZLEC%VzXSLe-_*vi&s^CLEAuNap_ykV`pWs)>uXOL?HRg-f z3V6qu^Md{8!`DZ4zidXL@60*ErAFj ztncKxvSlvU4*poHr}5w|n1Ssek$1!5)i{H?Pws{jamwM#A(nT>hJ!Nu#Lp&ndFv4Cesbgt-0pPBV9j;wzW9-R#6HhQ@7apOnEi2{5!QDH2+KyD1{=|`6=7=58?66Zt zO0$=)Vg~tw-k|9d!g24Paey!6!&Tat%MQV9^oNp>H_Am4&oVXd z@`P3+U#ILcKA)>9yVz@W?5(yB%}4GITCW{quOFH3&c7g3{SHz2q^o$VEk1vdSi(=N z{J!&3U!wfrr*zx{plp)Te(@}%|CT7`Aef`0)-F1`F`s(fgf3iDxx0~NZ0!o~kc(Vw zqNVMPaz?pvF20Gjj%uTie8Ap=&SvkIlzNjJ&LL+LdD*Z+-293*c>)tHrKiMwhvL1o zlhE5SO8(>}TjfU>{FbR$@ug|<6KwbSz2pi#T@p6)VA-}=zFi_VEU%)Nk`5y?x?iE{j-K3xL>udRan%pD-Wcn@g|Urf`U24wQkjiF(|_o;|p(U=u? z{Xw6RvEGI(+I~boG=rfiT_VmJB-^d+iFlo5hs=8;JK*mj`Z%RVoM%@} z?>Q?>vI{-Erba#RZX6=n!BO1jmXvv+MR@QrHLABO;8<#v$iDuAZ~~+n677j^n{O4_ zyws*@d94&lx>2d$Qm1u&w;I&b4{{%0p~!uyMxlR`L-)JSA>4k-IUCyNLm@h$V!)!g zR?AbJV+@-;HZjl8>ALx(I`(;KqzZK(0lp?XmgP$PyR3X^1h14y?x;t7xW0uvv<*xG zonr6meD)pCOXQt*^qLwQlD-$B(}?w1Vh%iaMC-iK#ixX|F>*Vj0^0WE6^dY=j<2ZibarW~c^7JiT{gQ4_k-hP>vUP)Cb}vy zK^fn#^G+PT9nXdW0OF%=m~9XC+>9GCIFQYt{Ko8G0W|Q}PIdZWC+viG7LUM1n2K9b z|NHN9D&9RI?Pm8#zg{^?^AL9}o&#HX5<&hSd8EF1{Wnk}J+a17=9E-i_>HGEZ$GNS z?zVzf$4H{e?f$ru-SL*G+;)A0DFq_rjlmy=k-Gx_iz)prGC0g3_~VCP$o~t_@c)CI zR{iIN{U0*J|3y##FQuc~8`2#`746#3&L&5WoFo7)m;~WDJ{UK!ft*l);U`JNXkmdQ z7XmCK87>>U%BK3JsHh6dIR}@me^C(k^*h%Ahi&6bTP!mbk;lnqWsM5Ep%?SbosY^= zx67aN?C+14Y^Sto>3835=)V@gN_@A7DM$-+_WM0DzRgQ??Lk`a<`nqW`#rXr&Yaqo zyS-RYhcb(og(Y3@wyda^*Rxyb9xr-)-!b;4nb-tL3+Em64M z%}MZa_j@Z5`;2!S`k$e-u6KKobbM#X@^?T>-xcPFea$-@{i~M22j9a;bnkspzTUwU z`iCWI?mk9RP53-f6A$7a=L2e!QH29mlJs-4Fkgst&;4OFFYy3>_$$+@` zD+oT{@c=o^Y8l@hCOY5A0KU(@y!RI~-~(OkqE_bv8~^JX`P(4l>kL`{bui8QfjaLs zQ`>jorS~byFXHC)e8>A09=-P^3SR<}>T5KN*Y_@Ibs{Ve^TLP53Suk%R+^9$pQSR1 zYbzyGKXbk?$bE?1=MgjGFGR0C*|@(vS;8CB8hIzs1Zt)1-fRB%&$S$Q-dBV}VXmFi zF}us7G|UEe2T?Oe#wa_JcT@cOtlGExr48>C;f^ui&0>E3gfSP8Hd_LGaEC2}UK}DP zV5cxJenj>jSm|FIdS}Q1U<#}iyEjC^7ke;-!5_8~5D(LjO$y=T+@_C96n{Si?O{%U zPkLooqc0n+@J=5NBdVc(oUVSV!e=7%+ZpBRk7rLA1F#XlC0)%v~O|bEq?_0 zE5dam@f>&r-G07kvEZE zH2!m1hwmGe)@vij`#^JzL3AIY$7#OaW0#I$s`>-~V%7 zi77r+8kMnEYA;POzbS+^3m^s%{}P7R=KZPf!+~N6_=k(vS{B*OKXrFOn;nfdi5vgT zTJgJDiL1PabtKV=mQy|-_>H|B6*YmzxE-K@#x>Ob^i*w_$V!?^g_cWrKm#v+%w=Mu zIeq1M2-nQNfMKO|?VW=n=YqX{1CN~1yjftnn6lEuBGS){)gR+B$sS-(a{@+UEWF^D z1uuK)huG2h(0rEM5hZf`|ls;yCL`O$SeITDX6$`td(g`eJ-GnHds?e=psF}M%Zn@kR>p4v- z21g9wYmoU_z2Ly|x(+@}`Sy^j@23gmi)p~`MoJ*^4H|#ZmX|L9LYogfOw(uir z70K|)pek9zPzg<|B_T(vX*7jwRBl?Lx3z$3M#wLMv%1(<0JPax-Q&t8L?}*uPC$*+ zv|NDR!+m=s*3h9qLMG$%iErs?EHAD>PP9>DOe|A+X;EWqX`^2E(P1b3{a7p4mWN|c zfGB)+U1=~WA^k(6i7PtnnhRq=N=Z7)WaZJe!QZ%TkS-W>-FAS*`ZYp3O73&E05&jK|syw#P%@+gKl%skl zSz+f82K6?ySC^#Db5GALyi9b5F&q8tB1Q+hMaqTQ_2@$Qo8=zGwCArwlLmTAlnc3Q z+VcX1dkA?y_(N|9@UttHoVJ~X0AQ*s5NhfpK$k}St05qYd@KpX9or7fq(}!-0HOzO zlx)pa-up#=9@+WX7V|C$I$MB66?7|t)T?B^J|t$6r>$J|)k_M}<9s3T zAqwr71`u@!dWd;H>AxuuINJDX&n6k!eV^^WNl_8z<^*P`1~e7OrCtHMWtO(blb-$l zkyn?svGbSD_aC8Z63n|zL2uB)B03aJ))2BstwcybXKZ!~-AsVx!H^Umsl2^Q#6WN)^Rk(^}^F4enS>n2zg73*L^ZVdj)C~ncJQ{bLvtz3ep(3ayFp4lF znn}$1buZwSY(vyBzsI?g4%jTYZ3eSRvauFcKsusGzjN$z@pDQycq%6*tKM?yDvg8yqDLlH0Dtr7EPZ-D;Q{@;sHQZ4DFUgY6dVW2EW7~tBR z)OTo7S75LZ>+;|n;_&PzkYEdW#PN<$5i=fsLj0FwCj)IeF@YOY|Ak^b2Z0}LxoY^; zH-?Lq!>^6j(5SeHo!c0c8tFYQE*;Uh2V9D`I`lt<5?$%j&0}3))oRfG<1-o=#m~hQ_sGsSD#7( zJ(Ij$ZGBEP+@56#?}Uy9;yP9r z9uzP316?nFWZ%aMz3rDGQ;%7nQIw^+%+V+~5>axL-iW0IC}nUJeJK?_zYUhdl_T^D zhKG;W!JfN<-Rj0bN(*?XGb^=bCji2TH`PeeJwg>Pdl~iZrn@j+S}{x{FH{Q4B5u;< zeoahdlqD&oq7PDltfCW)my94l^-LOW^N$%%l6aJ zONBGe8qY8rp34|<;U5BYBPfG{96mDV0u9YQH^bfD%N02Jv-D}4!_6A9*2*>_Inn8G z)4_vdKYGcy3~5WVC3&_|b&t&J{!i={4lKB1j-nvJ;(7Irru0^vlg@blqIVYc)aqss z)`@^8{$%4Irs?XQvXl$spMnafLVHAb`{c}X(RYwm{gfoq#OR1%gQX7Dy)!EW8R6%0 zCy7Gr64?YaAwgJ?Q2c;kD}e+7`gC+FMdEQiJd2|3{=U>u^0|x&m?75AVm@6=Jygej z#TQd6|5cC105A599Zd9v*DwfykE25cfXf0U-ozUKD{~8ey=Ub4QgJ6~XzS)LflJ(U z(jd=b5O*4{?y}aR6~A!cA?{|`I{fO~ByQ;+OlE3!F87y>=4Y0S6z+|R>$xQz4VmJM z?ak7_xVc#_a+Z-?QexLvY^{k*m6m}8vX%>jFpjd0LT6u1$h;_NkFjwj%7abMYyXAL zOZhFX3o{nBFrw$h4I*Cyu{3xSnsoi)KX#_fWofC&swgQ**fkmIdMa$>dW7a=vE}HN zxhoh}7LiJG*itKqHPjwLS!?io-=?}MYe}t<#<4S}dJFTL*4zm2OP&>E zm0m+T=Ld%6nR;<{vkTLU9qxci+1=?CB-KO!Q8q&zsdlEu!z1R=Qc9!MF{-Lss@w_K z@OUM#L=^RLyU>a%C3&WrraD7cvXnZ5yR6b#4mIGQCdHYltH8y#O%=9;T1d9w2&Ykr zm%f}TlfN+;@9Ju{V?qM=$lE%e%@O^NhpTZ6FMq}+q?2fPSz2=nk6{9iZfJBV+4C=B z>yAXdMy93~L(`4%R|5;j4I=Zv_CYxU-I6Q_<9Y@Klmad(I=tO!2Ya0TB9QZ>OFPHwwiqGDB^OHRB<<9r#&3*I2nC{^LKd|}q zo2I93@+t1`NGlRvKpx=3nVRuN5VVE(47^f`(}_C~T|u<9>Plh_M%pe+u2#;+zo%r+ zhYO7;BDT8Dp5<3K0j$kw#L{UL>*&xKMhr8i7xIO1hsv@gGv*I^Kd27VjwSYyy`BYnf=g&GwuO_u|mLKqm_vA+}=w zq^Y1l)$((@Xw0gxivL&-V8oIg{gK9np(}==E9c$*%cl+!#DO$) z&nkL^H9Zo*2)$y+Sfl`9j z8+`fq*Q=32!>yYj0q1X57ka1z{#$u$v1osVeGJf`XX+(n4Si+R?NaC8tFSn@pgn)n zba`lQV!U2!=N%JujZNGFeDiPke`p-;g3wPm!ykO43o`ca#nq zqm3bQV~L&QNz$3dab#CLw5lANq9Iyoh~6BBa-@Vklp{KnT1JATD~z=#ZI9LdJ+=XP zqL0)8k6e&TK*cLe;+ZDVe|oa^yYtZterH#^L5R!eLO;?s${wqyh+1~8k{c=Mxv=vz zw^DA*1JaD?tJ*vDuU|5-$8FLr)wpL1@4F$%sq`ZV>zFh!^5rvBkRZtw z5J*LKP6?j53!W1C@aOuFD1C#xs7FWNH@`=JugiGtm6h0}XW=1%;USlavvln&U6U;Z z1|UoqyPISk3&}jElc7SIBu+VI&yjLz!1I;g zou4_0er^TlUFL=3Bn0!!$x3D$_b#V!>?E{dHza2(jadp_my96s%$FF!2N+4a@%s4l z_D^;-{oIH^lu5tBc>L|T$|JR>yj_GrLm9_`Vux>n}HzAC$@W;Rq141Qh48OPWC3igIuxAY8wSA1DJI64`r@=4&Y*7xzs@*q6xt79h)?fWN zG%~yZ0B?knWjJy?ft*M~xgb2EP^d;|Utw&zV%(h(Cs`C~I+2`E1PA6wxR{U&>2Z`< zHh~%;iPkKYmP4q;fQJ!l4HCdUM??JjNoA8UOKy;j`wrJ3f`-s{KF3UFVq`@4GlhiY87>V(BrChI% z#uPVUp5Q0MzE0pfkJ6cU;JXmgGtps`G=rRu++lYh*8y1|P)Y?EEG-J)5ZMN2v+rq* zz-~~~hPV=$#cQv3((jRT@SQ}dv}TXCz%6hli83AB{n)n%`pwekUux!2LGQjdcecWB z->Uw}WrGAP_&A~D-3b_roJitaxgT=m1e_7h&NonKbN@<@Ob}w&68$>2J>Pgi(CN)b zaK}&B^EHXkFX(mu0Wv5&I3G6VXG1>fs*tx6aWGV*L4;St_uWP=!q$nvs=RAH{FMJw z&^%nme>>)alRXm zXastLM0do6!HINQJ<{_oL5!}5l6{+|a5o+mOBV+VQXDAJF|g$EjpzBV1^ zlvhMq%dfNB1N%FUkI9qYw+g}nciR02ZX}yw!6V|qBlrpwe8Zr3kYr8#^TC`k_ zuI8EmIF?9XnuupzIFKY)x{EJniZA#;C3gvxjDi>Kn^+iP#KuP?@QBxLvaI0#%n2C*4&s(VSRY2|#I##~3Y3!{(n^36{>p<*uc9k8pcu}C0d zgqZoMsV(THW;q2;<|r*}#SztIr{OYPl-Id)V-6}VAt}B35-}x`yA!CG6X+Tl6wHi^ zFNLX|hfp$tkk;UtP5er>X^_ZKizx5>Vg_YL7^J@QNC+VcYkzYnBQWb8nRDgJ7AA-O zuIdW0WF(gSG}j2+y)un3Q6%vl{1F1G{mIW#NI)YJ88kEkhqmx#PH@;KPjD!dmdrW9 zO$EcC3NviNkt6f~B^ygfrxI@;f7dFN z9dSjrBFaHB#ir&w(c5z_zZQ6Z-~pYGZaZ_SDK(*tGAF>s`|xLv4((|1UyP=eHG z-FIk&M!wks0h?MllUjH%p+H?v3ld@nM7j_>%#lil6IQa}w`Btc?~!vHOSg`tC+9rL z6}{|CIg6rr9s;cQKGoYVdC#lKX}!$uCWR9`+{K*mVcd17^H##S>zf7qUA^3#W@jn2 zXB!TFet>lW-lFpKCisHfZFe_+xn1Qtg{azdwYQt1*T@D?Q$;PV_{|=hY>+oTlKjl{ z_zgCz^0~7y;97<^=-QqJ@?0u&e(VbOg1cTz#6nOle(5;gDI<*UUIQla!BR&%hR1^^ zbb^$ns#EaQ|ga9s7uxw>AtnETGHOnNm3uiwn^m|NpAp!Cd(@_kJ>$CTcb z(<`3f<)WDvci;P5#5i>7Q)uc4rgF6rmZz7{G2KW(ap^ z3KcWVUIM-U{%TdYk9Cf6G1^xxp30Mw&kK8Scy`!3BapM{pK@;vFPf#JkcpzegX!9J zdZfgH=y|SZf+#)$G*Xd2*z$vuXXk{w_-8&dx0r3f0Nu(2J(VxnKwTJ9hk3c1Xy}e< z>519i)qMc`w0EW$zcHSKGR`#*%w)52c)~94Ny;sio>3ro9U+uh)s@i&A~{l}A^WN$ z`Klwz){60a;3__P_B${uK8Q^wvm3T-210p2qm5N_C~k*(dT?hRvSpLs{H<=l?TvRk z;PZ&%KFN`@bc8=*zn~Ogn7$OeajWm$AwB%0BfN3@ZMkndr=7s*zmZ}JW12zp>IZ(~ z_Q~zgl=b^kuvBJEdb<)j)hv=Gti(=%wllf1s|hnKjUCm*JZWYvHbXpvCI=Nb}xxCR;EaqcUP66(ypNrVLc~qs7U%og?FD)Q@887LrGJpsq*EU zdCXDl9l1U62bLRNeIbkyz&%cO`aEbU7}|r|2p!`pqZD#^#SvQ33MRZInRFbS$9{m+KM?EC^7ql+D{s>A zj}YI-(FvCcAb+YvpIByp$wj|(OqQ#(_J{YSV)PG|thmI7F&a@3(j;`EOA>KTHwla83hz^|K04D)N^>tW z+DLVcHjm|@ z@}1uSbQmE-`X~q(aFAYL`#c8>>Popvhvup$l6Sa9`FP}~eV!V2EpeM+CTjsKvIhBN zjA{!E!VA1aUdW4b39Je|2eJcUk$Vt=Y9NQ@lT^w36r^$@zyBqSu z5jD|l{)(6omxD@)333G*m=$Ij;$od`dC}zY(S(T(m}3}Zm=}`6M-cH2iRs?A?Lu(60t-Cu+WX}*%a&Ai5no3qN?Nr#&Pa=SF3%}%r0+$$iCrPPw86iP)87Q@9O zE+0nf!5cVfTo()tLP#gS?*%t3b;Cujh@2Pb2+dt6!2xN8$*!E7RkG<7x@QSKsnqW@ zil-^v8~GUM3Ph#ciTsC?8K_4kOWcOkWXooWe|+M>Q5o=o+uF5k@3!3B zLzzxq`Rc?=p?f~#S{Gia)Xe1V!5mh3unxW$Z7_cM>&l3u^%IyHyB7b-fS z_5kY&cC)ZMqI#XXP1$P&*Pmrhf=acdE~Om7v`xHX?OLl@cT!E{aSzShVI+4+dNr!R zsLs&Dm|ifu3xc;x+T;V8ZlrvN`wP$=WHuOd3&?$Ox0|jd5`FBLD@3_X;Sxow zjbWN?KOTEiwOOs^+E_iVyHBs7Wo2a@bvdD}1m?2@O0OYJ-| zQB$VZ9JiJt8=j^0KN@dSS=*iNr} z8G-Tnfp!L>SOzbY#0P5Q$+vt4R&#JE>w7HBxWXt#jzejMk!lv<;kBCvf~3T=dis$i zVE(?I11CDdM0W&ymiw85NrtDm<%|~Kz&0bUj&j)o(5%-4+i;NEoYlt%6UP5n|Aw(- zLtFDCe7iVc)Zm8#zReY{iNI$Jb2VUL5Pn!e7Vw9V<|5ROB~NNwG>jQvi_R7*8e)Ch zuk6ij$<=VN`mG%DxKTP|Gp#CVRv>Lq>?6Mdv4F`j@Q0nY{M6^q2d*Eh`)NW8=JBu0 zbHen(rF{Ps{l*d>oEU#*hERNR#Xl-+_0y${_0jYPNM@8$$CA2&LfpKNzW0}le*7bE zc;pUAk@~cVzg1b2`YaoQ(RM>gda^1#ft{A%%m1Yx{NV#Bc1CVc=vzo`A?rw*cPKHf zJ_jt5%*_|&wGxQ_rCPXtrpPS$GncB3VP?%vR+7&(sfu&{t#8C^TX_KotirZtgNomgH!$ncG;--OrDk!t*L zL7^Zo;NX$jA+YD?#A74E@{C|m4eT%IET4NU9j*P00d=^ zTt0E_*QtwggWa_|b1s_tK&`%Ta$Bm-$rFYIuUB~w=D-s+qgL-&Ot3GEbLr}7QH_=H zCgeCHo#1ddSS~6+5|Kf9&@$oPuR!#;uemLysV%0dEvKn1sHrWf$%35rnuzwAuNX}c z1UCR}q;p~OXd3ULxofolUc9X1%zYa)iM;EgPVMZ*IOeN>8#+A* zhgE(m2?)?#UG5mxzvypSzJv2+i3ou@MX9oPwJbBv&>%rm<_D^TkU8Pcdzj52Nn>pB zN{q=c9x%A33l*#nyMG2S;td1$NW+5exgmsr^X|#?PKrG|ct`S(M>CK|{7B<|M9JSm z6yGUb=R0FwA3^(l=qTB}BK2RWQctxG(O{^dElnOTviq|)S zYgv+_W9{3b9^Fy*ROm-ZI>{Gs(T=X_6nVQ$KuFKr{p|H>8s*8$st!~}-g@cHT8CwQ zoLzJ3U3TK7AWyFmJ5`$k`qPa-nG?D3#7YZn{8kSVdJ6~n-a-Ytn>K2T5-r?T;u*{S z3l24B+>wek0oEL9j`KCy59=bEowlv<&`=g(@eV`62?+tefjAvve?u{(Va%~~6XyYE zVlbx|l}YvviguMBV}ozIV#2CiOHwkx@vOH#3+G@A#jjr>FI8@cVP`}O8*-e>Re=UC1Z zqLDvM7XRA2d6X?w%}qZf8!w~B4pyyDHhOxhtc3b}Gr+vZuhhAHp@;HT00!Vl2aXdA z!x9X|;|;|V4aJiTM+~|c(UhYd(hX7eHH>*Gql@ZS)BQ_^MY2s~`n8Oyrr9FxYZL>e zW6s)!D24`xWiFYY?xyF$jSH4MuJjrFOXkDi-`*^#)zmhM9w zAUBxUx7r6Z-qx#s_Md{CVubxc#~*A}KzG9*Bc%Q1H*-J#lXQOZTp$^wN&Dx({R}-2 ztXDydtcZ^beuR5$^2;Pi46&CGw0m}bz1Bq>IJ;AF3cp)RKt4dX?d7Ln-R6g};yEno z%h6Rh82=8$8gY@0d5FM5M_{ERv(k}TY>O^;;8Zk5Ry>fBVXGk`_hTK80o46%LyL;O zKxDEN=&r~76OHW+h#Knv#9)QysDYh7gCTqKdnrohF zo9>7_h4{ey+WNgtf9uv`puu3sFGhAbG71V$j9eJK`6A0h+&XKIFidB3h&(u27M?JJ zK%7S^c0CMw;331TCsJ{TL=~Mz$W+K|nKIV?D z1>van9FxFZ?haxAhe>1bF} zpiP;+RjkMkEtr2%&5&xClj@f)L%AnWc+6c#xXs|Z>`D)m8_~Zko`?A-Wj;10`{B*- z2`78{S)4>>FUdVKKty6W6{6W7A~KE<9I7}~R5Cn_5;AU(eEvv3s->Me6^Wqt3qKCg z&7q6yvg?XQ^sJJ;sid8XcZ%R2Aa&F1Br-_EXvmtQ`m5mOKLZEpICDwl zT$A6{);Kk$u(M^+*u1cZk4*1SIv2diA*McbH{!>U3#QplVl-doQ^W_xG%3wyHTvKo z3TL*mmMUIANfutp{xXy$ZZE`}p%HG;h?QyvejPi*uuC(}XG`MbhEyCgrrV?%$7%*h zIn_XN zUR+$!Jz-nnSmUWykdV@yY+(sHwZXu^Z(+?ch=HfM9e$13ky+vJOucg};58Dqt!ZZ- zcT#ZNwiN2yh*-K7oZk-RGpQVJ*J9AL7RhLa4K>wF!MI{r>OoaGwV+^}dp+ItC^(Bb zOKOm=QDU7W!`t54xMNcRJ+6eFTtY`7y@i_*r82Wp)Vc9OnBm0CcqSESfFK%H9+o8vUnU1M7zP5KrHjlhehqgv5JyKKdtqVmsJ2^Ao;cn+}zjtiNFDdGK zD3%>BJSCQWdf+}?u%#K@XY}2<(dTC7FhtZp=NkSl%h>A{pVEcqk$0&fqzCHUEJrJ~ zPU*D2c(-d!L*K#_!m`}HCbOLlw+GF{ZgDUjWk;(eN>@U31QUBPf4r5gl#HJLG^)Xi z+m_^zT?<$6!mOE=%M8|7@`LO>xwNtz;R?45$>sbqBRxYGGN~bzW4CBdmLYwX@`q$& z0YiX^#I2OW!q)=Lakt6$`|MO#UiN6vC>wsi!GmGLH(WGv826TUvh^3BiUEIs>VX|) z#%<5)0dmuXFGPFNoHCa`%xiMXebgI|dlLPw35cY}pg(*wseV@i?AY(8ikv(cHL8yM zDPJU@%Lvlrh~Y)jJ*s&*k8!2iU;slLLHb`)qMpAQ@1w5jX3!P>z>2h;Kw^!1`wNus~&=a!sdz zNp(ccX3>xA0(gLtJc}s9!30j)z&u)_zYqREy=mnu>-`>jKWcYZ3B=uI z=pE@ihJW4sK=fmKQJ@1N_b|UG(Y4E;qKOoeij{QhH{KWc#fMNT^Zy1BqkhZ_7Cvity z^6Sofd-oQI6E@SXRUr3F#+S6#S;*3fB5KBK0kbjmLGZQUk~|6CImA(FAAd&DNHvtW zrrkU@|D|qHV?<~tteUpZ;1H4Jn&eEXLE{#r#^69{IE(60Ew`)}`qMuJeCix-;asrv zH__O6h%+^aPn9)H!<#|$5_*Ul9GT`P!xEOi5&+>?BZzSaD-yYyheZ>}F+ufL41iJ7 z9BLfJ`m47=Y~K=qqSr9ItNI7wM&sZv!3Bk{nQRvtK-AMhw&w+CsH>vvx06M#P)oot zNoga_K`xRRLgyH7f5SS^@jdSNm~;VVT--CS?3Iy$Q>rEMqkf`N!yfk-l|+zWEoGOy zX3)*!_&y{0!t*~Q%nFXFrhQD-1J`oIB8>PQOYiheTnldq-gv_NMGq|-fnhdV(1FKd z%BzIY_gVB!l!8f`8g)k4Q+T+(FUCjr$ zwaA%5d%u<$O{N`5G*CzDj)@jB#e?dM69B4)!K$)FjRy?dTzEsvLX%BshAqvYBUkC9t_i)&m+@8Lku67|9tePp*%u6k1?25Bi2}Ddy-* z&*TAKHXz&7k7uXB3C zh;HX&2Gg(H(4(ZYf2xK(NRKz(Gc~pxxHdOf4^1IwbZIh*b=Ed2&!gbfFJU)8l0w9O zUZ+EJ6;&R%VrgOERTRTY2L|l&KL&{IIWT5Xla9|kLK@e58Y!*s ztNqT?p}oHh-U^k<^W?fnubvyL1|bV)*@UAruEzA~gyNh=agoy&1D<2_ZvBMq{ez%| zQBbKPhjCbWFX~U-KDK=YuCaSUTYGO+S9P5_=MU(9`aJzn zGtiTrca%ah)9wDi$tw`;(p;1$vn5=P>>SxOvXPE$(7_E=nf^-+o$?`Sz*I?g1r?`P>7<;AU|80M#XOQB82QR~m7Nz>q=7_}$()+0QP31Hv zK;!tp7dRi=G#9Y*TdfRaU83S+U{}ti!_0MYhj!C+EA^&=xNmLa5JW*4_SdfXD+J{)$IW}VXY{r;2i_cmLDl1Vd7X}t$^=vC>pTkT6-K%KR zQDH2zIH!v%o;5`t5vNF!e45pZ(f$(D+%=`+kKX3tYRcCR)q0L_-op|METx2r^uMDD zjTOdP>G<=un9B%f==H_NvR03YAa4s5$FyUb2uMeO1Pq0?gFVCVfS#~%W5qGVi_Jv4 zw#1rUYCsPlW5zCNrsf_yw<)F4w}Pg=>W{9vM>~U28T`7#Fbw240%6UifI>snF^qx> zms><0-S3}^ozXEn3S)V6zMLm6>`{i%0n@uOUEB0sl9)H;S3CqBJv2Mgzi)M-VFE5m z7(i)x1)*BqKQca$a_B)+W`s4Ji5a_q9aVY$UB9plSbG1srU$m&4aqK9yV6vR8}?~o zl`dMYL-}rz3i!a9!xey#9Il{nL zrKZdka@|WQ)P|er5CCxixm>yx?GmX9sWPdm(c;na(Nd~}3_Ind636;+oki}H3_oS1 zVn+&vnDY`NLMSyS#V)F8YR!fMngC>WX{EK%m)wo;tx9zYjp2z~IeA)iJg|8U5EDc% za!xO5?FRHcoliyOt{uTeOb)qE)r*Kju6!4hLJR$cxV)+|NP2w_@KjRHO8p<#JyiFp zokeJbb65*d_28@eMM{~dI{sUE65aI9%~4#7M0Stk)545ZCs)Ocbp*mG6V=3*ak;lG zgLp{F;w`z^iwu$}!&FPl!xE0@>C0Ub=y=03GUv^iBa(Yz zoPcAYp7d0Z0?y*m8ep~B;upgocC}blMiOlewp?(+c`^qnZENLU11&xY1Enlv9$euD z8YTi=eotU9!%0Cf0yv1D-?Z93d_8LI)_FLb+o0CBmYeVAQ(Oq^_TOwc@zxP+e8ht)VB1l znyUA6sU;WWP$M=4`0au=!yRCnsoS+}VHh46n|t1|!?(cZ?FAsCc_qyBMZk?r_y-z% zz5$?vqN(9k2S{HMp$iYSh3_So;x4Ub3&TjG%0^R6Ci!@D#u^5PMcU#2F?O9~owQ5h z?F7u$Xnsk@ZEXz#x5VFnrTF*ee57{fvI~iZ2E5WEQC-G}D~bDC7dN@=)r)yI3unDW zFgOW~nHsBDS83E17^YxN^Z%)~RZDprDV*YCQn}MzcY~0xXis#+_Y!qECbOJHd2_s~ zh2*POGno=&Y4=wg-6S8QJg1;+)Uo`sC~iTz2&D>c;}0iu{mYR8FO7Ip?_M+`g=eg}3HszlH|3Rate!@za$|(lM z(XC3WHUFyl@rKc@X~g|!fGtheFzj(~s3IxI!E%nVan|sh6_sNkv=BK@U&BCM8}s1k z;U5}0J2kqV*rF5_>O3Sk6AGH9ltc_BOJjay+L#4oL-2zGNC0M+BgJXsveiJ}j3+dE zqbut}y`=w<(OE4W0?BhXMcT^Z!a{>NQp>$08My(rpytV(?Jh?NBcNZq`Y1m{X7*Y4 zL(!4Hr9A0|Q|6#zSrnDFY`KC=Ek0(c5Q=-^A%Cl@#A6AxIoZyGh{W3Xq|55Awse#; zh%8HR@^s{3sxMeAXfM#blbY%cAIUvXO2IZ^nyxEc7-WN3$)!V&NpR;oBdjl356W+eNW*hOMYv&rN_f?ET$ z$U@c~qaHmfX~s+XTNL|~Js$=`Aw6ul{=tB%D_#KVS#DD<^cQKgV|Llm6T?3|++bSK zgth|vGuaIaIgJthq&7bF7_4=6Y!Y_sCiZ3WLN04#Z*C3V)o48f?eH=k3g7$rrQt8n zDxNAzjG6n`R6m8CGsHSI{!Ib6WQc^N`xorFkIB5`YlJQ-DRqA@|KieFmREinaT@3} zEIT-PSLkP0e;DnQ$+$;;ANlDxX?cC^MW-=>+S_;59-Xm@lu4D+i84ipd8Q)K(%RBm z53cn}cUvXU!t>Wg=dY_I7jCc)Q55L(kGt^ zzvBW{Om)$j9{xaUXL;tg0N5Ko_^4?9SzP?Nt7Xu-&YCw?jUw^AL8d@tE7}Qz*XHgh z5`R0b%=@HYbyo1FzF_Mi*fg|O?L@_fIfWuk@Nn=$*LL<9PeOlRNiFhjQ&=@>KS8uN z;iKrLX*hM#Y(ZP%36wX--s<8Q-Fy=B#$Fn3n;=TAY|4J3O?h*TNg8p7@Pt@9w6t%P z|NZ!$5Y8|fj-e-1glkDmN28;?rqYt!bU6F7wIlYp9J`hL$M#FMpd1{RVR1`HocO=< zH!*$xU7;RGbMR8;4ry+k_NhtNK3j8fQz@L#u+G7o3g!-%zOJ?~L`5xS>9taL^3LZb zemKO%?_ytUHQn`cQV-*KBjCr>r_R+Y=x0(b{qq7U-|{|-<@nO}%<;b?Hv{@wUIG$w zcpGB5QpMB1j57=n($*@mPEgtp;SZ;jkdF$^KK|4LjS05sahxdvaMe<#fp=Nq?^55NSFVE)A?uN5r&%?K8A6IRRiczv!K^@Z2c4m$z+jPop5_b{>$K7 z7|7||ZkVfM%EJ>&?dI?k6e#6SmPJyTo;a>$*l0+O%$lNmd{noFohFLsEg*~{h(eGZ zQOPKn{+y1kj+=J!FR=PJ>qkNoNzo0E!qOv%tYet~wb_qB9-}$-Vz0OAY+wf4WmkAY z9@AP9^X51kUPbzrKG>@kjFxE>UP9;#>Ucxw|<;oBzrNwLfd9e{CUA4<3TzPN^n4985s_EaV$*iFP|qeQ-QKT1MYRj z@Rl|c`wEx4%nm!3Vvb)O0q{QQI)+&?&MQp!LH7^}e@eR*jwYK%5jTTCM&~sNxca~` zAa%AMJ)_##4AZ?wj6y&GwF$L}bNqVTx&aVI$Gq9UUILrf0O;){yp7RV)wNHyXBVHh zVUcvr1Vp`d>OY$IJPIzf`MX1R_k!+N z?u0n{#D)&>-$o&pdlzk|rqRIabz(X2dz0G!RAKXY!7&-d9qOBo4fBA9kd7S;(&A%r zr(qrSo>;r6SbKMt@%x39Hx69`m=a6Waf;m)hB}9p%42LL5^ateKZ}sVoVHqrDj|o@ zJ9SFg6asw>uArT2^f7(=ihl}wJEjH%)iv^u>*aGm_GD@iil?@y5+~$Hl`o>Vk`Ixs z)O=kTtQLP>42a7dPEP}v2anfqxglA2@YMKJLm(zF6Q^#tluavW8sjxV)zFFCP_j3s z%`YZFm%V(aRVyeZu6+`th&HL8tluu4wK0p6gJ3xM-*AQ1$}c8PC=LU!??iOSi2ao^Cg84z^D8~#iE zn28muz6IwCIXFdRpA0_@e0DKHZZr*pnLLFExjb;~e3f6te@o*gYum^$T*Rh$Hp8lf zHnM>dvf||N6Wji=Zt>_8*+zGhSxvh9eWlaBjY|5HAUI7u@l01c)g&neZctY!K+2U67Srzb+bp3ln z*6WD&C2_3ZLT*T0f+dm#(vU+;S{vDTMX-WW368MaC)JGoiUi0sS3KfL`Hhs>jug?^km2jF4^k1JvtZgAcUkId4^NHGd}7NdquFnY z`X`5bC}mOX*Fh9ql|2q?t*szdFBgw&6@(zXn92clTcKU=vAE35QNc0sAFig=d74D zH9kEDc{yc@pi$=npW4_Ig||$eZqjwt)VSKqo2r}2-5%c5F&||Yz2Bq~oXb#wQlZVt*xSVjfIDX` zB#Otk!b~Y@OHv>~vZ~lDpHg_t6a-}1+!C9|%3kAPYx{&8+VxsJBo-3q_7vhz({{wb z!bpDE8ln1DjdDvG$TWg5`CqpjD4k9Gt&Nopl;88GndVDUyo#IaSy+)8n;N939H$VYSVIZECGt z$(nwg;O6C8{;U(l_)%{AY8Cg(tcq|h*b4g#9u--vWKz-ECO-{<^(G$ZX}%CYIpbun zWD4a|R3BG(dahS153E*0ZNUqXJl24%T(YO~Rpo@%a<5!ThG%>Y1#l9&LlEvNG@6{^ zbXvDzM(Sv*=5smd_xaXs692QJa1xqh*8e{9LLdSJxz_#HU)ngZlX!(IHfXWudANpuU`^ zwi4^*Kk(HO9xL;Rmzo~9zTzThj#ztp1b>HbMRYsOU)tPW@o4rrlofn$l+*yat_Fp+ z3k85)SWt$E_DsHfd?Wubq=xF|SV-^g`E;!Ry%sVJieoK-W+V|)t~WSls-=ozpw-e% z=T%LFcqqN}K&WS5Byxc*F0o?{Ix{S7ns&}s?eTv+dvsm}9c?)yULTaVb%@}1|60?5 zW+!ne|3RjJhE$Ljv0VGEXqR<9`CZTG)V|dxWjrvz`cC*cr6%iiC zQ(Jgt3)r?y1e4pz+nGb8~vs}#pbY@cZGVomU> z8@yGtweE>+%0^#;{ltsUK*T6fvnNw~Uw+*Y)k=D(1sUh>tfb-+e~puJsE;9%s&h?M zkSewK46)dUE~&+xXwq=C3^q!2WWuYEi9$Ld=BSw!t&h(H9Vdsb&&?cHOeK>$Eu`8t zBay}jS1vE1m;v($`*U+H(v(Ds>w5DF`K{;nWOPUBtfe_CAcYGjO>blSyO%f}?A?JD z1U?%IVn{xX=U2fOnTw^45!|+p%J;^PcifNv+9dQ1PhQN5go;;G@}GPxe-KB=3Q#y4 zMas&Lua;az?Z_x$6ZRL{H!)w^%iZA?ynSHpk{E_Tct`;kf^6; zumkm!R(LF)d5C~5q0rmSzfRdz)E58r^koaOi^0Y)*dsPMyGiZ~V`B*IliMuXIJNa0 zs={{Fj;MeoRo7PM>B?(Q{fb-Ybno~tDZ!0?7-hj++FhHO*;O`H`<~|zoATT$%)sQr zO<7R(Vf@EBpX1YcXVR-~(FI)+`cB^=pypJNFgTT&R8~FkZ}o6GwLd29sj_5&<_!ro zTeU#7q|$azr7)H#^8lrRnscg@uSi=4DMF)@!tW`&P@wl;1?YRz7btA6%#PkBGYtW@+7e9ySXW4yuXDx)#*~!nQ9GMR{{! z7}RQhV}pOWneP}$!Nr=0BypwxokM$v2&7!tD_^MmX2S#`xsF~!Yhd#fWY z?n6R4HF>K2vZ%m1m+=kmrofCk$_vaPU%%%Kk*(xfL0ry*Zg#s=|1_#zTBo<`*e9_w z!%|82eFzZQoumL)8IC=<=P}vIXLYQQtmeVN?^vW78%~CV_aq~QOg-dVc+gwCto6d` zc*u3vD>98a$X;Oa^_|5wh@JFIF)3}VSYMs1b>I0tn!T{GU4oI@b%Dj`k>W~@C51%g5WSHYxjARyEuDxrF~ z#^l&}9}G-Kr|@;?WT+=sD!$U^A{6g{Tm?#N&(|dDRav-rlleI@c2L7^{aO2kP-u7? zU__4XWwluk3zm~M8qi`gsOQQAUys^WCT%@iwU`2CLcOIv=1AdO`|u!E6@oT%vVdeG z>E};JINs>*#5a&~@M>$mPq|5_Bx_lNFU58jkip7njmu{01d<$AK??$^papLVia$^* z3W~o_&j^a&Q!@&RKl`s16u%2?sVw^M4Yv8rsjvl;1k>uhx{-~WYzbqDJiehPk3u?j(7{YM+jb)gROlgjv6nWyrB*MK7lX%kV{z)8{xglR4ws$bN zpMvt+eEY>zD2LH>*s2{k_R-2*R%m?nty9OS96)l~KeADkKr~?Gb;R3Sl#JZ5FjoX^KQazM z$u)~yfD}@RGaLotfy(iM^Fee^G3E|3{vw#Z8|qWoM;fc|Fq>VJdK03*Cok4uBpWzM zjKxOe&IewRJ;6K;Bu&$1oKTGFu>jl{{#NVRx1Q7tA3&y|xoMh)AG0pS6FcXv#tEw~ z=@k%1^U=n6MTWQoCrRf^`GiWLk|>scN~TgFG@&)^B6s>$Gl7r}m2HXC^W<_enU#Mk zAd4w0Lg6dN;9GX1y^us4P41{~K4bFGFip*onBN0d{dF_`fxW;32_8F!5AO8C}?r|Ap8p=Sdyo3xM zE7?~dHa$d~KMPtNv2w=6HZ-vtR|Wd=orNvywgpkb_D{&5m@PODDPSx1 zH7y3_sqR1rOSJ4GQ~GD3n76`6q%x!MQ6b%uWX3D-*+&MJK{gZ2gnAqpE_ zlsH%vKe*u89t!6-z2=6El^^3)Z%?%x&N@+P63!hJJM^>qO&@aFdtb16goKE8a5wvf zi0>>m?bR^~(}-l4*24h7klO*`X;6=NxF0T{*?JO_aX3w6BRbFxZy7rckW!maVeQmq znfq5z=L2T=!K8CM+l6qHn^`PBcEI{+MCL!HX3qfv?$TMj$IJy7$;$({# zAhQZm2~H%=XILa6jFOJKpvIdN%@KN#6gE!S@_t1^gB0{88zNda+Rx9N!yY$fI^&!p z7&mn~Q+9zF{%9{v5l{%m5kwu!><_$A7vK1Y78G&ctb!4Xe-U`}GRdnZXWbaSsbzhY z+viLuvbH9OUOp3~?|*3p{G^y%9Eq_dfAd#wv?@DI&HGZYbda;$#QX!1;0^l>!uhZQ zEqJk-VGpr7Xq6_EQS&kpJhIWt9MCcKPgJbRIMbV7q+()Kt;7=J%MTlI{~IkE6lEfE zGQ1LwHoKK>DiS}t;0N@csXv&A-I+!&fs)zMCSt;qKJ&&bDAWgXf4TE@!T#yWPYQU) zId?pJa5*^*ep@;BhZQbF??8>$B)}J)_6Z>1m!y2^5+U_?$|+#$CE*zFNju!2fBY@&V z-Hkxp*mAo$=|WVOi7?Q;? z4_x5cj?L9NpDE7;5_#}%o4d5gZtSlYzKm{bg2w;J%&bTC z&yuf>$CgA6k{P3w{2+UTe~}b%L?q}zVlKr4!W15@0QnL8?Eon|x6P0RK4jz4++`~? zH6V{H(kTU9KsHiN^?s>ThEK&R|3v36()Con@FB_>0gj5`rlbn zkJDXYQf2SuI6VRNPD_yuwpb`vOCC>01nfc8bOd58u#1*iVJFdol!vDRB52$X#)F$4WLXNCWjR9ceBrZS4B=b9(3)+Di_X^X6mF6Q+K2?&6bN?{@yOJ7JS6~|z{v*>k@8MOh1|{y(4t)O zHkx>3h1>{r2}mwv;#`N|NXE3Y&-*BP5OLxpu+Am8^du-n1V5n;%1Xnr&8qn2hFlgzK9gR;rQOqp~ET-B5x*W%QWphnER_!)tyo{xBZ-5fcM%ywM(HZ^!?U;XIhH0zslh zk>d$~J9VO#=t?%)j2l+)Ar>Jg-}}*W#ZF84g4;~UR^^7hLX3f8IXk8IME)eHXQYA2 za8O@9V4i>TP8c&`=~BCQt$Ln?RpRI>g*)nlJh7~RZ9fcae#&K7@;AN8mRf?|1mZ2P z5An23FBY8zr8VVxm%47wr4byIr6krtNiXx0i<379$~QfHybJX-fD-1A(QtT;Z{2a2 zXxhn^3^5CYa|6dI(CJ*Ka*q@kEEz{xc!Q4f1d8yO==p}oLeAKp1~kUnF=@SRJQXbd zoXA8Gk};#5EqmB0P`f}3H|@ZzrDG$#NfvhIOa(3(=MJF%>gM&)r)0X@I*%0h7O249>%alWDj9WAI8CDopk9<`bJs0^L!-xsDFx^h? zh6wE|a3nq3TJL)CY@p>^Cpz2O??Q2FpcPohe+$e|gyHuF%X;fUraOhT8{OA`3naO4 zS1}h(Dx=R&{3&Gm$)F`&=Q~Ki^!z+lWcy=&LPOW|6WYg~IeY6S4DlYL2a;ttQH9vZ zNX~Kg1kR=ZNCPwOzM9V^O5x5U)9957;4vJth=Y7H^i9sJG;gk1+ zszgkcjK9Nzit4K#EsTjCZgoq!BQm1v1^a=>IxKTYy+P3nu^pvrUUdf}M9~Yc3hz3k zJ1ltDcgOxo9uzTs8QLH=0xr^$@qikZ+I#C&V%} zfn%GI0i>^P*%&7fXw0=?SUM(xRkl%O!Sx}LWW68n4x(i*{EvaAMN|q^kgmfzc8n@K z-b5T*6I#)U)F1~NNYs2W+;7X9xxtn$ZF#Pq81cx4^|&$nqPtAazBH(ZL0ozW?~L(R zj$L^X5i(`K{8z2Cv^2c}<|Lk7OihMgK=O!t$ZaJzcCU?_PNJv&TsAb2)64*`>c303 zE%MkE6#jgl$-_2DlqzBmzU#CR`&c3s!Q=mPl|EwM$e;i6bHrYiFJET?8Z9*ItK$Y8DPQpuSB+w$LMMaXiVseuxG?Z>=2a;9Bcho0v9;U&#;=I&P8sBNX>6M78vk4)Er8~ zfFK<3CkgD9r=kK~&VCMV1FchrY>Wqje!?0Grl%mUABzh@(_RfQIQH{(#2N{q_4RI2 zP7+qNK;Gp_hi{JlObt*TaNNHdQjh7V#&(#_D;ao+`_Y*XzKKB^@W$yogxzCS(8j!7 z_H>e38_a7i1pdb%`j?J=@Z(B9VAj~pnr<~D-bx>dtk0J6*BP1Xf^cLLJTyJLyxH;OdF`ssU`%=f}=Ym z;LiPxT@23~eLQ5jhuENRh&BdN-{ttx3o)C(?8oUA*Vo4mjMk~?G|0^TPOkwzGa2-P zTibA6QMp+E1S;1L_;C`_?G4AoKRYG)5i;Vq+H#HwZZNqM=CQiiaszjrN;U@PaeO`Y z^5YH<#PH^Cjzc!sCat$8*DZg#aqPnY^trTJu7%K&VmUXAcVZ>Sy(&Dgd-BwY_L2+6 zPgYE(XTr~U&=L3=@E1LgmyU$-cue9`NV<)PE%VYBq^k%55q+;+P#Y{L8=O+<`nlcB zChp?X@HfB0RY!Y-MT#b44nm*fm2NPXv{J9J$D#wJ^;2pf{3vI2j6oZ<&ndqQZvXG8 zirCOQ(z5Qx%W3VulwScCU&D;k2=+%KY*?+RWnMSp7eP=JkLrN=33Y)Yv5O#t3Qtyr zvlY66g<>y}#N~G(g)>CD0@hNm{-ou128A;b!2G(J!2b73%!HEe=FmF3OEj46{JOfMZ+V|$qv8&w)i9~udkri$<&TKaV2 zG$9ab4fBVgf<u-weq+Qh(>MHMdAZ-El^Pmy5COkC}h| zcmu@@{T#4Gv#4qDJQtMwZ}POcx9gDW)n8tQyQmHEPpl~6n#K9lm#<_ZSKExsObKKu z6)_S$q(#NeqRNAKE&yI%1LrBbrKE8cGe)70%=8jjT7xu){5krkhG-5oa+t@3RCNZ- zkiG^>eR|BG^s-u~0s+jryuTVNa;)<*T&E%dG^$+Lp}hU~jm#ujRBMQh zmow~6LEMC7KtcG4!eA5a1dFX1-vN+6ZmY;2vKL$wDU;6hmhT0;7?nSYrO(IOT|fYi z+85Qk(Yob-6f>D7v;^TJrLn|{F9%Rvuice;A@@=^hw+vt-*tEi2T|>YJ@`?Ae(+HG z{~l~y)_F7T#z&F=P3G@v{f(UGtk~9X^CEqe6V%UWOY}~=u{ExM<-3U8@N&dIz9Zt- zf#|j_@JnKp5KQv%Z8a+YN@RDh4M4)aIzpoq29ICPOk)y; zzOLw7)D9qse?Y=G(Vt8&j3r-AWK#%$&$^na)-w$kuqS28*mJXHKv%fdOFSj$c+LWI zz&s`kSp^(N4qC*b-wdlihxNFIEN;RYo|j%~;c6c_Ygv=m+DYz;{BWD=sJASkzN)sn zcWGV9ur50xkV@notqQVK3?N;4053g}l$w!^%T7gSE+R7!fi8ODFFirTqYQ?RUJkia zG?lD*DsZI5D4E=|uj=Z=u_O;%)aGgq`?D7-=kA8#Uw=*h;)W+vUS?LlI1blsQ_yOh zWYi=4p@%mm!XP8ylsY)s9+!@?ZD@9^J30wII_VmhzK>2BN+$D9D(N#C@12l+ISP;e zJCH)~i?wi42jl#pWL}7%DbsBkNz79PfUA#i?zzEO?goL3)q-Y_^790RyKe*V>f%?y zG4Yt9CNzX*lQg4~I8@W>X-u+`f&4Jt)6I*jPw<8VM!b83+m4*3Fa09CicbxBOvve=H&Mq0r5YyL7q!{O{aMu0iy&~Z$4Nsq zO>2KDc5mFB#l@`<9D@N9!@;X$;d`5e*CX9Tro>ySL%8QT)IiHXrwBY(C$L~CS5WmF zo&wOB_1V`F@^;1t7F}k`bjxbOE|1p?d)&@2$ijir3Hk8L%P#S4Vo{d_X32wuYLb#% zo&@^(cqxtHOfp6YC18eDJhTfgW(w0RUkF{{SU&E{q8d;&2;t8F3?VS0QH@Q0sBvoz zB5?YlX!b)0lO#h?k_W2L&HoLaCwUtUnZzo!l{Srcd*~V@AA-h)?P9HZPbLD{;3*MQ zszqV<37Xp*!E55c+2{GooTe}M z4hKyEPU&IEu}JH6>eS}p0>4wg0C zmuT9Ss=e??y-!;R#&;1=@gs;DQ4Hx+!f8|_{A-o+3a5q4LO{vN9_mRi68~MRa>wAu z7R$8RD@SCoQVRz}h6hUZ2cGghAHY&iapn#fzXBM|@7 ztyE!Pq1`AwW+c}2MwG`-yK$nZ^M3SWKXSjxxc1+Ye4PSPDRg;63QmpAB4@LnIVh{n z#B#j8gTIo~6!Ki}pOfA4k`*Md)`!X@gfqzd{UJq>ypnE)N%vPjt%T#u8<6%X(@dqY zZXr`Y1-!ENqY57k7iB|mJx*TL)I_2n-YIkFLXoT=Xze0IqE8-dzxQ838&QSA>rQ@{ z_kB8VZ2etB(GZkG-mkM68eCt2no^33k5|M2QZ~I%1WxH2a-}hOJzUYS%c6T~X(8m9 zi>oO{of11*YW(n64M<5Xqa!p`+w?N})ue-;%~-T8jKGXvIljLK=Nrdwkc0F0)J~PS z$Yt*IKP#7*k}pG1x}32fndrLqGL>VxxBHGQ+u%o@RGWprD*5fQ6_{0r9E2q%krYZ3 zfkFwgj#19!^t-a^RiB)353I$L1L4$PDTQ<5=QLl*B+8gpQ?cZ#E;0Ajg*0>!N*Yo* zm2wBVR&?50)mli^>PmXGqk|mOIDZ)2-F{-AuG7IciQf<6R4_YfW)zFt!>ZEL-_>Ry zxYA!AQdU3)m3l=rX3e{aT|k5~?CJr4mQxBeis~(TwA6X;01_s}i?P4-eVcX_s582M zTOdxLbSja|PNw6X`Z=Q3=p{E1s8fGw$FwSDse>G>nQYHAoprp*hVmi=AUso22J?SS z(7zI;!qC5Rtg}*V&zh7}1SJu;nl%EAv9p_9oJfhc4Y3dm@1VG zl=4Pjh_`ZU&@mni-0b0e_yeppa`g76D7g>9!6If=O_yingu&ksl|`CRIeZ3S-z`3b zpN@?JJXlP<_Tk5cvC{Xk4IJ*5j;5{H05yxMKT&Aus)pmI?b+1m!_%jg=+r*t<}`Au z0nzaPsC4sG5HN$F%R|V_OV;MZf8)lwXCh_6LQ6Y$5~-#EZrs;gx9VRKcC zNYR6VRe``%L9j*7y(rF4*iAP>CaDh|3Gg)Q^kBhatTXFwA)t0eUZp=9(w}~BGUx(p z(D^g`JRR+p=z$K>V2O#%Qape^(eTshA~Mlw5R31@5YcIfV9lyKggr5MgY-TnKM|l| zfr-yY6IrOu zzl{~QahMqYvF$XpFlxV`Brb!#0-zZC2(74J`bQMhzeX7ei!+la?SNmzQCr5yKG~E( zyrrgwpmd#65?fdgKiG%Rb20c^v3Jz(fj4pWD)_w;z3;w3ZO?F{5c)jq~?#OL!UY*JzeHj_JH>=epDS_B=*k_ zp-`(Z1{YwfhA5gSbWar2D3nU@CVd?HW8|FVWLY?&&vw9ikSq2^;O`{Q?)SFSdF+f< zvU1`WOrl!D;P)@M+<^rVH@lEQ_>j(IMyXs>KuhYAQ|6Kb`SfI!G+@k4r~+-1)XwdJ z1YZ$#OYev7DTyzshZN~U=%r-*x#YWQa5Z{x^=j+_Wfd8$&}s(co&<^?A@@gMT5T_? zaTY3>;yW4)9qEYOi6)(vh-A+vKY-D6Fz7^)&+r|AMXNvDd-}KBf462m55hZDpJ1sB zqC<+)tap_Uj9dGVcdZYE8ehIN^r0<&CuVv?$KL#dGa_C(ntl1WP{+|c`M6SSdPHZ? z@JAgZ`MAeutVnPUVVG}Ua3*hY!Jr(Br}}qx)1IaYBEs?$I@ickx6r=%nLW6kF- z=Q5gi73)n*IeKC3<*qe%TvleN0L+d3z z(CQE3sd;|RT#a(tOzFqtmotil)9CVC)y+rPI^{&h5f@NyVHfwyj#9`6fZbY*I*8v# zz4zV&d+EluhK|;lBGsE3?&Qq`6eRUn%^d!0N1vZ5;(~NIo<#vhLjzBz2Rv zh+L*L8!JGgMge%Oc0Q|s!6OuqJM0S-d}5blzOTD55mXH{+y^n*ynD8`MkE;@!`L5s zEL7<*3}xfmDl)niGJ^t=4vL)1h|K>Tclaj_&8g33H;4_eBWt_5AV=9<;hTH~Loozp zd7Vz(yQm}bKb%m^FRRMuq<-aZ6NHEG0<}wUs;6>#13;UNI2{W_uj3M{BvwvORD{Z2dJ=@|>D1YlXlTIH5i{|=}_GMO+bRU9? zW~0orFS3&^?;s^!v1r2{7%N4JsU%Tf%^p-y_3y!uXPr0gK4g^I-y`JDC|dE`IW;b} z_m{>@@_n(-I=7bmmnzwHJT1+aE;{uhg9@v#`al1o)oHzUGN9v6X-6S$@Kzt-Ed&GU zhM_@*w2Q`QR%)}s6OhI~wcWC+n=k;x0sCqvwdIghca)5`qhnLobATJ0(sVT2c4ddi zW*3iRmLe%q3|~u$_<_)MP_pv=>^@Ow_Q0e{`hh31UQl6U%9wiZ^Kkc2Kj00Q(EP&1 z&ps&oH*DYSO>*c@6z7sCoCN-i$$g&(y0|t!F2i%adoz`W*ai~%JWp{Sp0fyvV~8_h zoUX|{O2tD~`SxL>n*>5q-I@s=7d4appf1@ibO9nqIADv@vNcMc^heXNddfkoVhQKN zrYBmR2r9`l^?7lBQuZmf)|IP->$CHDV?U(iYN~nEU-%>)(deLOPCB*M5vga!!hvl1sb7;{7S;q5hs;5~hj5Oo2znv}H*K!B9 zGqOUw78p`|0LPHOmhQ943^%+chA9w}Y5Sy=MxX10>k6$Oo)csQpY-jh zq?QTB2ox6%q|{9)&}`+yw6b=m!An(2%j!0;BB`0Y*`C36<}kGrQ@%P$;E@I9`jl#Z zH2-ikFbNFm6F_Zvg2`eI&DJ%e|8ceDcgft@uO3=2(#O9%u-b7>d_!ryeNkNy)8?$> zkkc`z;4HoUu+G%x$JP!Nw~j$B?})f*fbq1JK`#3^OvVfi<5y9?1jMupTo^!;I>(79&{}BUG0h zgWJY_*OHCuYIk%Xd`-2{m>f&mF7YPK7_r}$`Lj~ls*>j?wZn>@ptFGDnoZNqmVJGd zj{sr$oE0UPDB-U;i+2Nj#y=2FF+6$i?H(Ocb!Ic|{!Z;WQ!MMJt%Pk3K6yGL9ggiz zKRVcy7)8;%mN%$VjFt#@_uaY(l5Q?&jTlia6}!0PKZSaJWlgj=$TIHZlw^H`#R~e( zJ!PTGR^t&X2Ei`nQB=rOyp;+WSQu#;0vai?yoLTVhn?W<9_9~9yHOVBSO(XJ)_$mq z-V79L9>q<33no3Bz)B&N<7usRekgTYhV%>0s<>qg7U< z1*>+v+n6wPA3q@pybNv!1w7zC%F8K=Bl(N%c3+VJ85MoSREo&I0({ZgJO>PVu+003 zfNhM$$sH&e?R8|zJp{|`-}ANw5tf}`qt1`cLKD*MBk5z`W6m@QPB{?|7;)7%?)tJ@ zab)QMlq$B-LWt>EN5~VapQmJx{Asjtk8(P0I8zBuzgZrr84<3xqLpI@%o~kSYkeR~ zzhnTF$_HzUp(`B{yw^07t1dB^Tpx-Mme(b__Q>8<+^YqD)Yij^9X?j@x^3^_rx(#j zs$BVKyEUJ`+eFj+8WB)$rSM%fOD@($DLF(TOrjB&$e2|Z_0o-4>2AD$rC{6oO~lRC z?xLh)*uSk5w%4Fy!JcsOTUgJO*T^o@f=y&En{{F!XcKy$DBd-Kg_+hn?7!3A@N01{ zsimt1`Ui`4CU2V*K*IEIW19r6Ka{eShz>`q9M<>1(f~E723vdHP%`Jp_@~MS*{$iD z*(bY)WfJ~wSu{oxLr6r?pIVOT!M8|tDPa{Pi)&JW> z(0kt;ObO#U=|vgo@)^$?(&Ad}Cs{(sE|R(j*QmJS@kE3f&So1>o);;wA3p9;+)QNW z!4xq%eAqma)mD4k{682wr{G+eXxr{&$F^Un3>Qwb} zKX=cy<``J}u%EO>QJ?RfW5af8-}1QQ>_c~lR|J#dAt%Q*Zi~A`V||*TpsbpLp-nc^ zeIv$Ht}T-XyQAjOCCsa`v3}qSBy5l5af@q3by+%o?Qen}?pRQT;@hCNu*>1gp|Zxa ztrJ*`H=nezQ`T`owY=~P8_?y@`hm3@)bRy9a6K*T`FSsBFbg}O3me=D`909g&9C#t zK7W9&i8b$~^o<($+Uf0q@7jc{CO9+yh4J~1( z>gZcXE+_kFXwyXYBR&1Ikta;?^TMa+7j7L`TfPF~hB4&Zn${o;fgmj;Y~7en*1Y34 zRNrSN6(7RqNsvKcE`;#I0HX>% z0P;g2y%IiX>6s_J20o1Q!-7K?|1ax@hDQ16kmd&mhdBSlv)iZ;*{&R^wR7#KZHeiu z;0FoE(7Ky!kft}aT5zpHW|PepI9a`!TrCj8>`y!qxvcp|v?8{P8`s7x+y|%E#C`$2 z*&m;s8-idzBdNkZhtv3i4Z@*~U-j`j$DxIZJ?GiC&gJyLLf{XQVlhS~V*>+!2OohPmpFOyg-O2$78 z1R8=>qz3lg?gY0eSI8ftD1YE8!romx50Iy)??L$^DUhKJugf&Nm`wSGrZpzL?ebcq z{J2qTW+q*ev}cKba#ZZR>wT20Y&O)o)3AO+kR1xi-;NdG+i`rCP+il4u+CF}d*C=N z2-829j`EM^<@ABBpHt>V`O&>UsE+ah^YZ-gVi%`Fk{_TBk-cbAPtb(<`XJ8F((zt& zL8KqY4z0a#vw*r_)(?O~mM^W%D!V|CFD}c)>d^N`OuhOp2t9Ga&b)nK+q3qba1UPZ zl6{c=)8>A@FF4EjW_tDR2un|Z=q0yCVsBE(#cR{-$6p#{zj)J2ueCqlifl4|)4E<9 zpxv_zQ5@-0?@9p)4#1~8S}SwT>P-(@#@PShR&!zHIp-#6R`<1%jve$`zB`ycenKF< zJN75ZmY>UqjN@Iox$R|dOkb3SQ<{+$qEpE&VOYG6*N%j(-dMUJnd zC12`h9-l5V2m6N^8p7y|^}~c?h9@x1(}L%Tm~t3iZO~y132n^h9TRe$8+D0jjKqPuS-Rbo>}AZ{!P*{5WNh z{RNf-ybjTPQ=9bK?*o5(wTt`$(XILgRz2nIYkjM?|7Z2idfwef`-V3e?AxRJwqL8k z4-JC_i^B@U9*-6g+8CA(R?K!Ji53szqPkSM1DAj5H->{gug_wJ46uqG)o_8U%hL_zFT&8K2yHA=R{Z>;A{VkQYHZ&-qW=o2OEFkt5@BC zh}3h5wcjjef&FVv{1vi?C*m+9H|qR4SWmDT9)u_c9L5(J2#LKfF$t?U5ZVM=kD6FF z345!$n^g0h+paAUx;5!&Ns)k=N+LD);f(zR|FZakex6C=ib`@9J&XhR)XdnUQoasmeF-j00ty3RefM5<2)H;fBW)dA@ zH_9>2Bp2u_26TiGGR}Z(5bel>f`y!5NHzpx45fmlyiaq8qG2Z!)-qb*(4Yu>8J*#f zq6pO$^XH1uY)J1A#$$j3S&!{x;8Tp$+i+Suv?3HgT8qPg#w4&I+;Y?i&6>vWepGYX zn#TA#2DLh>A<-18N`wB8C)GH6E))a3-f&d(isTT9ZK9o~c?703-$6SW`mn>PUDTS@f(nPvb}SAysk28??rlY>jrz17$XUv|o4L@q5{j?5&h6L!>$$ zk(4lKpLMBrHp`%&OR#z5f)2dod@aXiAz~>*Hhj1DuHo}qiVb(ES22GA_ zT>|VVpIbBf`_!R4^=3jaGX>B zepY@pD8rPOSCm=AksyRx9i;O*cy1KL?BfZnj}3}nBex5gJm-Gy|C$v^H;!#tOv+dp zGSFBVL?_8&rJ=3a3iV1#Z7hA54eTTA-d?u*UeIN>fgas7n;@kWd5Z>DT*C_`9L2-1<$+3y8QX(7bS!7fh0%Vjc3e;oYH*htH8`i*zCC)=?=;O(3yGfgYP2$!s_M1@Y-!; z)XrlU6n*XDWW!5?}9FyK&hPbk%ZOiN;#3syx zK-3s`|H-^39LB;IO~W-gtGzK3e^T=L<7#hXzVQv4Be^>gZM$Qu_hBFaw^29~?{Xx0;^~=2W5sM9 zfBlSb9W$~fEG#`d{c!^5h7R|59(Y5OIpmgL?iF8p_?!43PI4?CZ~hhNI}S9??i)mO z=>8=9BT(`{?)0Ny-_H>~d2G+I$pJ$hZMNDc5u_Y+3k?o=-OIQL(xb^D-6SC{PCz*; zy66BdxfyHlNGd+2Nrz|~!Vd|-$rwMjB-PW)o z1B80UM8mkWAJnWeYR!dXTlA`xVR*FA8gCWkoTij@dbBIuXLFRMNhm$0fz|)S+JtfQ z2by~HhraraKT^=)>I6KN%k8nWz9DOMVQmIIEJDym0%kTSR-%K>AX7KNb34Cg9k)>i z|A~>=Fgyd)c*X|9{w0{GiCu>6vmd639%R~=VV4I8Et9i;`U<{k8c3o+r+pCzrY%Ew zkItEb#{e#fe1^wk1qWy@t7uQvnWft_Hb8#5;VvfwzAyc7kN%Owf(<&|N)F=lMaZFo zZR*_x%(+(u7tjjX>Bz|Bsf>jxcngxYlY=YSdGyPkD5A3>q(VmASe9QdBdW*=Dr$-q zIq8CuqO7PQC#=YcD>@Qa$OJ7~f+AW1BTDLD$b>Cw3Ku!4gOcKek^&{-ZCX|6G~pN} zWrW}=M1E@QMeK8Pzef8c&T-~UUE3fM47&wGt)nBwT*l2ubkJaov3c=AZZxQ;+OQDr z{yg(l2lMp_ca>RhhopUe@@svk<((Ir7s6^yFj*Y};*40F8CB5K0rHU{op4}2VDV^E z{N$8wR4X8r_t*5KL&5yN-TY~QLvIHd9phY>^)$3W&_6^QCi(#G8R>n(M<WgyWz zlT(G-fJ{Dw=KE!zXbGZeSoNVS=aNCtRw1NGqdA=4UxS>KV(C&T_l+`)qD-OCr!0Rb z`|pNBE4D#HPVCYR`23%|5#(6$f%`hZ zCuhV97QJDtX2ujf^ZWrPNe&tBC_1J$A(t~{hE?w{%UN?nxF^sz+~4Q*i_cwpPCboj z!L1mn45!}R$Kx$HRZ=zKZi%1jT6&JnMj7_tQK#f*H2OAkAgKmB}nPqs1-3@$Q^QF1}C3pR2L6)@MrjM!&(>~)L_5$jUvdwh%!nyBog_pV_051ib z-Z-^mVG34$IPGdmXR>VYoFO!8n0VyG%zWaeId3mZ+cXt>5N6(;s%FzN z=ZqjjfDe_^CT{k?9td)WPl)UWdQdOTW2^ezTJFDBZI>3v{yX(kura9KOKo^^jwxjO zHZl^*%@1gLyv3U~nPR)zTXF3rm+CIn3?X&RNQaA)R;|L>2&uLwU%(`f))vOTe=|rEPoMLMAR#5_g_GGYwURzSQ$tj?iuASg^imD>B*|s-~2w z9O|tEn^A=Pi4iUST*NV*?GNIg#+QE;WyA%EX}S8C-I}RspY=&;+WbkdqttSvjl zrKf0UX^*wE$Sf=?tZq?CxpLP1|1;v{oB4&B@tXp$5z`mV%u}8A3UbecdK4sj7~`E7 zgC7{;K8zrbjX6;bKv54MFTzw={a%A)rW-cb5aTag%uALGvJPK zx&L4sx)g`4bH~sV*Xh0qMPaY|#+x#8HM#=!NCgNx?@>4l6u39&5-<1Q606w#H?~Yx zy=bnI3N8J>E$=J4xIK43n>63R;H;=YaYyUdr_bj9Y3(@H;i9n}?SfUqS?NC3uB&X>GjTj-yRCY0_kT zEi|)(2zypKHdE2&EM!g6Mf1)`W7O4{CGQA1N6@6@5kyZjno7~+l}#LUU(Ut;bt1|? z{llugfWk5$#gv}f!*W$&Az(QVLUlj0T(a-cyUuM9nEY1iZpl#~ZRuJbml>3F* zv>?{Ym-UFaaE#A26#k}(U2rx#?+A-ciWow0ewKA+#bo`kqQYzRJZ~Z+pT0T>25g?V z0q?ZD+nsL-MqHF+CHyCj8q~9;vk{Z2G^buIKHsP;3#^qvy(VLa0Ef-DghL15zl2}? zi2@qJQYmkx#E_(~5491ex)y=!86UMnm$Gc5{%c%PP^>g%3-@5dRkQLaTalK4-=&gb zPQs$x2XD0*&D$@e@xux+UsQKS0iszNQcVHOJ}?H+JqU4S4B{ICvk}L`n?44or^LPY z&glQlI?Y!Fl;!*vV*3w?TP5&82W`)kec!9bz|Pnou2CUJoVIyLwaQ&XEFb^%Cogmm zXBZG?77*RFc20z3qjKm#lN4kf)BzkEYQ}dlqnm}ron=SMS1e2VTMy#jCbX}yKJ1ZI zxdCB6qXh05qE~daPUin$?3lZK1&OnUL^)6-jK5yE9c`jVnT7)9+Apb7RtI}zO_VC>FfsCRwL z#JH{~8JEWWtq$niROEV>RVuGf8eb1OVNq$8AL@|I|fyc#TQfQ4TS%&PQ> z7qt>Ccc?q#?BzB09Zx%bBXsqyv+T({W{)KKq+Hj}f$Xj7+`sra16HOtuj;Dm+1%c& zXe@K@-lV#uA(;S2h<9EPo)u5f@n`BlC^{mFPK7th{vsTm8quJ@XHtGA>ft>V#O&&^ zQkI)(K*!ub=WO0o4^lmGe91D?7Opn>j+ItV$dJ^twh)7U2(}PYf{`H|&EGDFsx1Z2 z25@-M2l*5i*|wsY!Xh0pojf`fPoUEI7H2V*bVWls^Nip^*}^jOs#(T@7b%=nS`>;U zp#RO+hEQYlsM5^=lPwdhTE1D{R$o_t2Apd7)a>`J==tfd$ksl?no=mKG+2X@#ENFQ zJr%>B;UOLS4geZBpnA z49mG=?)+ zU8w_zH{m2?RE|m9gQ1$P3Iku@A+N~XV>#X5cC}qw#J9r7EJ=3U=2LyH$=K@E-H$a> zgk#0g9IP%)U1DG9C?jsiu;?DN!LjSWtE|ptyLRMm*24L|cCA;SU7lARN(zRCqiyrj z-&RJUwIU=P5D(q?=N(E2HoAPMmmNx?5DOovFckUbm_Cs%zwAuLdnn>#FevoJBD_I| zj`>;*>x`ne;vgUXR2~VcOszVW@dRf)$ZpZ^p1htLeo3$usQrMsKD4dT><+QL^W~`nITKlKntB$tcE_inzQW-g-tPYov74>jqC zlTkDm-F-@agylUWot8cl{P@%;qMKx8(yT%7N82V*KRj;=twC20vFlj1VGi7J-aMks z8_#h#jp#qRbM}MeeFMF2_VfElS+Csa3tBk4i-1Uk$9nDf=|E25Z0&d%5`iv*<2bsD zXs?IkY=x;6wK9Ei1@ps-gg!JFWI`#_HLA-ea7sju_*=Gp8sm*!Ok-~Wt5>0%OE%t} zT|k@H-^4EMfZ&lRUA(58n4vUeV}I3HWbq<aviS{xvvmbB;X>bTg@aCHrjX!^S z+W$AYGT;A!uB`54>0;_6Waw;a^8fJ4;-+?{PKGY_PISgLhR)6{-jLoZfal-WURN`w zbxfgg5Tq3bNeF8JHAx5r7$70wNDGWXkV3)%n(D~N_S+R$mH*=Om6Adv1!^t!<&_@5 zN=a=^tE+Vj9mI+ItdIQ;P4M;a=lAoF*Xy?XZqI4%Bk!*BjgS4q!Cc%8D1(sZf37W~ z0PNN9q<04>d^FGD3M0fcxx=AzJVk0WXUa1~X^|Xhn&)}ZaQJ6PqYRDN60kImpgD7= zEHS$tB0-i2vsSM!X*ksz3b3nW`_yVFTk9j%!X)-6xkw3UuwPrkev)>8+|AnEKW z^ixOk)2}v2Q=4z~08%@1$6JR8Te^lE`<-(n?)GE)_a9xn z1;U$?#k#jU>)!4$;+vB9g<~I@mv6j#?E63XZV&PB4>4gp2Zjw%@HskdS)14Q;o`$Z z-Wu0PaUH|<9s7sXkv(+x!O}bvK|itNZz%Y=CqvBM9;w)TW4c#|6MSOveo?>a2iVHq)bR}VqgRy|8=$Jk08`*X9fF4Dj*1rAwPiN`wKcrLfOz7) zTtzo0+w*8j)klzIWogjq4FD~yZ2?+9awKzLRiG&Bj3~6pjK@V`Ye$c*sh75-mk%$y zWPP1^aG*4?I$$7F3@FY=b)`8OABMGswYdhCKV3z8b!Sn?)wITSAcZiXFtYOmRrGm4 z4a;ICa5W9=eI)LK|TskYW>AX!zI1%ND4 zWnKoxJO+`F=x(NHZ9asm1vrXh-NVpn5+{Rb3rm|XK0hfamy@ooOkb>AY^*dx!f>9v zL$+TC5466dBEZ(H-m8Gbmgk;HBFm^PeWiwM{Fn9G6f_FNbrYuYVFk%fU6GeNRxZsq zT7_R#pTH6pSHu*MH8y}OHis(w_xsy}Qi`Cbuf*PrRcerZaN8(wpO_3@Yjw+%J?Lgj z^ip#uS*{a#_k%PswZ0Id-o%1fFMX3U7)B)DgpCcIW*>QFv81GiU0D3DRY%&cHXCgB zE%0|rJJ{$`Oscq|B=SzCLb9ExgU$Dbw=3gQDAyZ!qKilasgfLM;tw>a7~U<83IC|( z^^C0z@HNe1Z$#F(Kq`v!iO58Pf%F_alY}q~1M}_j;)Bz=w6l*7^`?%srJbH&*~$u= z(<;lmimHRF>~nL&e%N^h(k12bGLmOpOwactk#!i+AaKuRVLf2T8LI!dMZl|vAB@b_2!DY0|#Nec^~8(-^#1pdg{`LW0rrK z^1r3H8_&xMxYyYluQND4sdl^@(XJ5QN-ghyR_SDFBwiMrbT0jDq9(#lMWpHgCicLnrt51 z#Hxl*;ZBS2=HyO9B?Zvh+jBJmh<>4fyR`YV5G%{@rheM4gm}ij4ho`5_PeKOCq}Ud zDiS(##o}{qoqv{pzr%U*Sx1*o$e0SdQpNK~jkVo&_J?WO!s};+Vi)@fh{IJTsuz{9 zkG*`X)LVnqkmAkTBu;|2f1k*k7o60%lPk`v++dQOb&iHr#A@j#nXyf{?ViOizcBM=ye$QJei zhTnic#ZJzoU1A&s}Oa)Y3MEP%(t^F3pEWS0e6(BCFUuc zJEkWmbml3kvK}XAlV00$>XFJyzEbf@fQ^iW<*uENC`*N^pO=sz>~a#ZPAyMXnIv*} zWDYBMHi3M@F$V*-u>Q^|_<2N}RK4{`>4%8TsDAttrB@r0_86#oGAI27t;?MJQ^%x$ zd87*V2=PzKiKBz~xMdR4bA%<6k5@V-%p>KG-ZRy^M`EKQ{SMK73UJAM)T^ema0=1Q zv*Bd^kPl75k>RFbbicTsDTeQ{YA7}FnUc)OU(wpFi<6V=kNFFnb1BZ^qfjxqx6$J@ z$v~AYiN&~WqPP_;&Y9=td}NYk3R9QlEm+H-*Av&wD03wv-AT-BEz44)08r%;^eqw^YXg znYtsH6(|6Q`^}4ZkcG`t6bqginR`NeaGvxxYSap3K<}7jYhRUWjiBjGVIpkZJAFtX zTlxj%+$IUnTXWpdcMu$=5cUi#+@;n~JKK$OKzAzEB!UWtL4^1idM++bSMYqa&$< z6F!dPf(?|~QgE4hSZOHsHDkyIDA^gNq+X6Xp)nOLw82`ld}emTm>oacCm@=u5q-=r zlHJMh28NO9HvE$`EKA19R`;8U+A>ercpSH$m=4N#BbJ}Z;qZf&he*T7Jpcp5Ojy}O z-H4Dr(6o?$@TovY2v%~kWd z`Jpkgq9sMyEMcoK3-~9-w8$q}V*Fbft9>Wid(X zT-|w+VgeA@DfqNL-d2&6HJ6%esVts22~h*H{gBruN<&J>g00RhUYn6I3IMD`vP`{n zW|pqJr077<=JYIfrIPLpl_D=TX);EYYDxrvp_kp}tt z4@r(6VD0JAfc;pVD}D)+SPbq}-7>boRRZu#HG##+V5|YJ7R8tu-6%@k#4*dwCZ^A* z*0Q!An$EIRe?7DN8w6uSSm)$sYHK=E-B|gc#Fv(QpX!;sU&hupB~i@YtOiD1V+$V` zSys@}U6G3p6K$74H&bg~_@B_#EQ#242`ekecExu>$k#xt4c=su{Z9+&@lsi8O6pvu zk|MXM-gR~)<#EbAawEGOM33!^Ggc6+tfAALMEja|w%+FAmQ8{)%AMx~Sz_^l{d2@% z)mC`4eZ1lIo$HcyCFrR{8_dZDTkJ*kcPd$G>uGaj=09euQ<)6*IvXq7=c|o>098|E zGsQk-tFy{xqnGZ+jf}@}u97C|swt|f398vySCC@Z8r1@|4xKDi8z?&CTltuad`~IB zQ{x2$iSZ$gppu=I5&|a)^O>f!^m-`jH8l}8-jym3@+YWddr4=YdT)1)o$3s94gk-p z68gT??Hw^ltBqd5Bef~e`ps3;V~GKYj#UL)xLhTefd}Aleg%^m8PV>S)UW@z6hW%*DHIB7wGMCU>6biZ{PxNMWDP1 zN*lim>4P3EuxE(ja5ZkrW%WCkmwGh{U82(3>8@qjHeu;+_oPyb%H? zkaj;*XJJY6l?|e9bb-GKp^|=GNM`BugGEZ6V{kRg+XTfPt<=a5YMPgRuNSeBpI2C0oBN-+OVkCy3>vfpl4N9?@ z#?PnamVev|l15#Kp}R^lUnEq^!RLit5;Q6!#ASG>@-;%}@W^ydQSg$?(BEoMejT;H zyln5oB<}GA6Z;WQ<8k=IR7rlVij85J8e(O7_^jX-N_Rv(i0S!V;4&1Q*RXSkcE)jD zA|(ht;pF!uz2MlB2yC9t1DccNikY~!b0juJnLRMh=Oly5!0`m(Ly9pCw0F0_nCBt! zg*hFd0JO_!$n(l_H4)SG_&P4#;a9$lKp#j?gC!W@Q;aa`2vKCsJ1tGq&(~kOO%5`!NHhJq4#0q=RV9VRYvR;)`)D*4^pe<>zjy#x${(Fq0 zj3LZozh*%&oGL*uyePq0&O|}C1VOF|0rI)ngt2&AGT_fNh>DfnI(2H9}>`%mDpkN1t>o?5Pcs_bWV08%8 zO$aG?h`(`=$n=ozDjI(4jDu5btIiWlgCLVydd1zsC4AW9)7j?rhDwHsh7=&_Z$m&b z2)qx*GEYhveQ#|syKaqw={%uK|425_MWJkP*GGgq&bdIe>#BLtTPX+C6sh_uVfzZv0CefmAqo2!VMYkz5f; zHNZqMV#kQ>BfKLxjw%`rz@fNoP^lsC#=%L|r?WTh1*DgdUjJ9kX|X>&`qZ==iijDgVa#V(p7mU6;_M6QT3M~`1ugJwXX0fjsqfh^KAoiR`*>-rGGA))AE8=&V5WrNOmTjbgkVi^J{t^r6~t?m zBGHOLs74f2L+;z2&EPku2HEUL#sN=rR1Al_j=$`I&=v)qQ+ji7{ZFTfgi>J9V%A;g zQ+UB}UbRlhO7OEHm2AMxC+5fzg3y)t?Qk*1^G||M1!#7M%Lwl!OOIj#AWP>>fna$U zRS^x4v+s)aBgIz$dd`oHHBg+wDhGNXfkaL5{z?v~taH|)6)H`8#=II6p0{29SuAGY#v zUBuIEOy;DEIn|P_xmgk)MOk8W!(wyl#FhfQl6her8hcaQbw-a>*I+h<%xCk}wM0}# zTJ=_Fws#-Dm*dx#O*%RF(Y9Q4Oh)1H5j>F~j||3+D8`LQtVeL^D@EjpF$HQ|QW8HR z>alT7_mbO#`$0BOKw|4^4#n-ke--t!Gz9j!JEZ1iy)<%=!OJ=11E9zM;=d-v3fk{R;%silCQ8iaIx8P66H9GFQ2)ubq}t_fXt@ zr#KlMO~vk%QKoSBiIZEz>Rs|7Z-kOH9r0G3ZgI#q)F_qkBML|q-vax`XN|=Ed~S5E z&KwU8^d!zF5;x^O5L*6*zeVpV)u-m>pDvL4@9|q659cq+-qwlokLOQ1;;WBaYEkx8 z4HE}z@5q5Oy9?6ZQtRX9!54l6@?S}X_opa>z5OQlq^6|$1ybHINh5s?5?|ng_pqr^ z_7MerMKH%+HVFI(!oL)(ED>Z}n8J5J>tTYf1w~cRw)%GlJrG2|WTFSjTUGS(B=JM!tmnn$g|xYE;cevZ<1B6VBPwF;jE=MWJD#+1qk`y_s9i zl?=XU$eeTzxvAb1q}{JT87)>>sugYqLB04mIXX7)QMJjfLF!jz%bpu%E=JOtV5xN- z3fI)T^^xu7(_<8yP1iiXMrs9?34v z;%%EnJA4>rCq}7r@eN#mC{uzHs-SwUi6Jghd~^!X_VC$NYGKDNrC{!q{k4+lc(igN zUfIH4{*?Zze3Ct8s)p5T$LhwhhDp4}N&aHhm%ThT5c&er-7J$h*X1|>^rktJjF$It z*4q~WEwCs$ugQ2$E4_@jBy{&&MHiis25%{n%iF~tBvGYo%f3?6Owa@CwI>%kxZ)Je z(wtVz8-W!&nY0P_NW`96IFpcbBY3l6kA|%q#qrzZ35ox(@0m=X=aVW&RHhi^GGi8| zVkqvY+>;cees?i5x+IdGRxtm3F30~h$>uGUDAbOKkLqWqi$B!u*x-hs0ZV~G>P7%f z+BAOQ$S9wPLE_|4i-4&83&E6y(_h;7vhbvBd&+d+@ywC;)D`gL%@E-UdFl**^29oC zNqy>kWAOZzyYX|cTfN2QG=9!}^Q&3qTD(+3>dbxWjDG6Oe%f%r=n(_C5v$|p@N8*C zKX}@3XE)RGd>)Z0uou}&>Rhl!K5xl*>P!^jiF(?Q^0X!IsU_>F<&4(z8~z!(cqSL0 z&9ddB?J>gPlqC+(CJ_ZmWQR~fRB&q^Mtbgha9BTb9-ZU}LxylUi zVh5R&z~uu1kbAs8F3H|D$sRAsUJuEhPZ**C>INdnvMtc-f@TX-ipF88=I9J*#Hn-y zugL}|F2lLYL$yFavwN(wdY}PNgiX-i5yrV`V-1f8fk(PPeU!j|+R`;y=Cz=NNn#Gg ze<c17!{Z$WO%O6;ouS;y6Xm){M4DqNFFg9 z(zU3@#mRMp$z_t0=HvHyCEHb$c#zz za;2YukV*aqk$3c2h2DP6$uvdkPc(6O+Cgb*X(0ZSD3OetQzN<(;r(mLOutb<6r_zG z1_CkBiP_K62$vHRTEh6&c-7&}M2Nb7No#eCJbaEDjQA$Tlfbm^7;>ZgR#s_c~j z5E_LZjdHSm7FNtCfC`QB(tPQQKv!mfsukI+y4Rr)Ag&5vW25#dvThY+Tt!QyYUBn) zTm4V4s$YDnWqTHh8~bKBs96Vjy)zfi_p*AOei{^;NNd3|wlL~yuVH)TTI6@Z<`tz;nI*LF)c@16q&8V4GXKsAd4_E!Qq4LvSEYANCDqk4O z&~l5KJw-?VT7DAGpU?`=tQ<(C;=NoETCoal1j!Wx3$SmYCT>{6g@s-*(k_M@w7Go0 zRw@_i8P~9_c;}`)K&Rw+G~-L81I{4wbnn^2>osb#eLr^IicHg*L%`j=^nf$7Cm39v zKY*H39!IR%f|JRG40$05k_Pn#SXaDBzC`FE9nBT(5*Q;`;d^rerFP!$?VY3w^dS0gSl4>@qZ!F5v zRKzzIH6ufhwql*JA)L8jaSB{Ys9GVC*NrA(4NtHN&o;swt(aoewCz`UrpYdDji1q4 z$ZsuB)eUjI;IdIz% z%G=hxO2p$0SYAM~PkqdLm=M{QC{vU+A=6O z1cT1m-of%}OFKOa@cna6G?Isgeat*0P1+EjyGLI}cPME>ds5%t%Jh}H)`x4m4;f3f z;?pU=89OYL$LGQv4foU-;Yv6#-xp(D>gv*i2jRCp3~!T?oxh$#!ME)>ftZ68O~nSx zS2S-9vmN7#tD815qVUXjaP}EdIZlmBG~zf6vbLFhT=8(uFau07n2p2D%!GxM`BJpfK1 zZ8;wS+ecp~(S%xw;G4x$wom6L7ELd#W2os_R0(mTK{&W5juPX`3VBcSMJL+kT*c92ZR;G=>-4zr+$v&5Wox1*GtJ@dp zC0W@kDfff^kkl42xFcHmfoJ*Xvv>lIQROQk@fjZf!ie~=>rnYYDcUoKp!BGN(6;~LYlUCWHT8fAiWH0;`5arTeemK0xE+`6D?588rnKWv1O(k#0{gIl zzGIbqWyUgQ1~_UVm0C=smzLyT(F9WDr%7=5{~U~^ z$9R|Weu{?VuTQ}H>p>=^dmDP)%rZnQl!P=qx|w~VGwZF!E-|(AjCAHb`9<}ujlaej zseV3POH3|%mzu_@%*Hj9RK~>C_I>z-vOx5~?iUL`XDoP6refvZG7n}SGUwFE$Xtpl zbv`N$n@b39Y!~*utDxknfq;;$S)hV>&aXT9F^f)PPvGC}kI= z7#6X?u39cRhOr@YHz(cst3~K;*jz1C-W9XK_pMgmH>wfC>dSjGM<9W+bw?mOgqQyZyGe9A#mylR zt%zHVcws9{PxJBk;;e|DEh*=*CPqz*LEws%+bv!w4ifz++!;BfXmV)Gwp?w zS=Dwp&ovo}C3{i<8?u+(fXKC4vQ-;mkez6U8+c{2?Qk)7y~HPJ!PGb>>6AvNEm2Cia}i5|*!x{aqQ0eK zc$2NMDH3}hv2OCrO5RK=WExABXbak^SrkZ?jo0Y9Z9GN&^$VqIw!kZ%`?$*7^6Ll7 zly|6>4O5NXBYIc5+5|9P5U%uVM_6N)tr%C<%*<{4gdG_zB2DhKXVCP?Yey=Ymv=72 zgr||j3bEwMaeVa{q2?SR$>qa8B9bW+ynIDj$=WDlwjKXo6Jt+-O;C7RdQU5_@!mKk z7w>3@7f7O2a>ep*xT5B=aE&~&Fz-~GFPwD`{Pj1`X@H(c%L^2mRb66?zIf9Mm{tqF zVay8|-NMdb%*N(gVNXz3bKe2pwa-?aPjJr0n1{$GNFU&b%(gEPB{M3kBt!>8L|iN& z3!+dBiMngz5Dp5@{p`sxOb);8JXs0}6N~pTUL;hm^eTSb0Ocy)qh4ViJU1_aa>ky~ z#(w{K?d#(&rytE9sGO@4=5_9Rdamme2quS$r= zM(s;RJKo~I#_Ov?zB%~gEE#g_h|hX&I6Rd6V~>oaQQXFF8XrD1_+5u@Yo?F)lMpDG z;gk8_X>V`b9xb;MBYpw`p9#}__{1$i7t-{w~Tu(5!iwqB$+nBOv8lJLG+_t<>XPdUZkrNBIPoLO< zS5D0DBssC)^y?QN%K|G*9{)|7Sh``f&1>-kPev7#bujwCQv#;-eQ8nj1mWi2ETeRj zo8G}oZWvd1cZ54!023$=;p3*T41a}zeXH!r;)RWGA!wf9C|mdb5Tm=Tm)AJ<90KLGeCncR4`4P1q815&_v-zoeYacSgeUdA{|8VwJ zL3M`DpJ#A)cXvIw`@!9v;O-in;O_2Dg1ZF>aBz2r;2t=*v-$0RW@~n8Yi4WS>Wk;% zdHUw9?u+ivx0{I=k`LqDvAu7SHpr`qju=~Lw`?(SoxwB0(Jy_}yjtla)U~;#L)oW8 zJOU#0+9m(;VbC>QiGzpqz$qe3(zzoal=~YU2g5%S0U>-B*)SV8`0aH2IQi{GRb-X{ z?)KCxtzlJ+#mlv3^^(oYy@s4hzft1$TLcF{mK9&QwF^2dHi$yZ@J}>E7!#&AX(k4! zrzOiGK`JRM3qIu^9pWNY$3kB5<-RXY%JIUani8NvLJN%-gs5FN! zXe*V+7$T0fkVfZ%Lj5&N4*tU7;B%jLowB1VfDV<{u`g)&XYNTQceUlVCm#@axM0ID zOay0zh!gjwY*<#$#M2z`l7C~)NEv>;QoWX|3E&uNjo7s?Dl4%c{N^v$3g8Z@0+nFl zVd3*+8;RWVU=Q`7>uX|}ENEikmXzA}s7$nFLN9b<(ORNl6e{$E{ys^77n{%1xj_KTK!?aS+^5ua`uSz{sxP7iiVy-3etFy^f3;~QeBK@yB9_!iLU@|$=Xg-s*)=Z zr}esB-=yEpP3oAXy)!K6n6>1G`_shGkPtjY;!}ZB;?O|4(V56^dU@ohCz)9c!~8y{ zLmO%YQbM+(yUhA0mgrnBb0uuTr08b9rUdgfCrlDEb?qb4LWivIu~|1rvA@bti3(Db-Q2T#m`6UGdSy9(!VAs ztnZu*%RJz_^L^hg+`C5c=_fkEizqPTgDtr_hywP+B)ais#jNQg9(1H7D}f%5nM^tt zS$5=%h!qwA_S*U)zc148q?8s0mZu@A*K88K!K5*Rq|iw(kH)P3nZ*pK8AQjTj)-;x z=>p{TxQ(#okh_y;U=@(NY1|?Q1}DBI-qzv;ebUruEs<}%VreslqN&<>2bUZrV%zS6 zh1P~|VR#B|H9L`Mh1|CnQOdH_sk68XjXN^I#kaVnq_EA?E%=exj;XjXR^M=tj!u3m zRlEej9NFQz3I6QHi40L#ZOOSy`#xlrxqN{TD+jj~vJzMHz!*69^HZuySgcA|w(6ZA zIzmiEQk-n?x7Z*^b}-y*FdiWSE)|7sJdRqEaIA`B-lZ!!fwmNGn%95z-+HllY1lTx zp6lS`=*qZlpSkOOno_n8n}a{XRCr{B@8EF27ZV~Va1TH>YxwIfARS=-#QsP8Pm|5yA2A`A>7dGzLaFlU)yyicB>E;$G-jbN zFeiRSWB@CC#=oQ6r*nlw#GTo{f{faq6jC|-b1{u10B)Vg9x2+kncq=`?V2D>&w!$) ztMnYn6SMruV(fd5+uTUK;X%SFF0AF}8)Q?{TJ!`T*Y&!yl7^mU;F!;pUz%(Rc zn9p8>KW2zpnX-q2cBTTky%XP;U!@}I2|mR~pkW_szBAwCepiyPC~w&~mOdsPjd_I^ z3*0o<#27!OSgN8Zo1w#D5le98_;G@b%!zAOl$NJb)(Rl|B+VcjQ0%j?0L>(IH;nduY;X;aD0l00d;RcZ<3tk z+Z09ZRm8_M)S7TwqTBvgD2gedtJ6tSY0*;tCPJ<0J>s0!1N~Ol2)R0%Np+(pi>~<~ zG%`DcJ20NJH^VYEd2d&odrd?BJM%;!qQ);(+T zws}jcjFW)?H}6uGSZ#?Ls+4imGS?o@e&<{^<_pltwTe|c#R#Lz`lKj|>QiR(^0qZy zjCVvOP2?#^3nT0Dm`yKE+GZHhC!V&9XzIkWMk;8Z`GAOpo_!s#sT)2wY!}D5oWfg2 z=ingK?Fz9>lpAHiHgGsR)Y0DoEf}W&Zg3?a%7ms8ZYEQ(x@`Q^N zTLm92+B!aR=zN$J9MLHA%Up<3goiZU$(<9RCcshNT-6dd-P)(s1raLMc@XAnXtu(= zK~xV-o;KE9FG#f0Yw`g!42+fL_aEeXNm~9)Gl4sMwq6g_Kc`Sa-> z)|mkcFU=ER&7TN@9>L=gRQN_&7;`lh89J|5t(#qR-Q1s5TQ}#fu+Fk!B)K4~nQ5-o zR;9i~Gg;^5y2Ka-x|Ev$+Ru;P4#|&XnD`B&!$hg2b~VN29k?wY(y?JASx#fA2(aHB zr*TOVm;*6hhhEM%KH^(`N~vfHS)Uxjyura>(D0986IJV9@tpDAb9TQXQHD)i!@(j> z01@)fcXX0rnWuO#z>6qouz7d~oC9Xq=J=2soA0|$2J?$>_LfIq3Vu z=!tU%k?$J-=AYP}|9T&E@5FWvqwRU@%gota;MvE8n&l(7_A-F(D;S-4xf=($K*ALa z0!o=alOD0hQ_$d?>=R~`nViHS;ZEOcJV&C<)iPbhm;ajSDj8A`uT$z|P+3guAd?0eHdm%Fct2c_4SmZe$4zhz zsWMr3-kYcI+~npg%I-<59zTegjQ&Ef$%yve<;spT*^~Z>~$s0dgo=lSU}P=3S%OG z+n_K1(e&hPiTc}ce)%G4_djZSO7dA{{>ga$pO5U{$=unI&C1mMpKiy;(Sgm*)ZG4` za)-^_+0By8{hK2j)c@`7KQAEv+cQ-KC}`|2@bK_o^8Y7y!2i8FiEkd3YM!PR?vkcX z&Q7-GrVh5gmTuIVre+S7a!yvx|D_bF)v{N?GQc*f6mC%@iTS0ef9r&+c)N(d%|+sd~Kl!xkh+zdf1NS ze64AH;6GgZL@R{k3!Y%`*b9!}A-3QHxn%9a52{6&Bad+B{U#qomkimZ9%LUZM5bc+ zt|1pk>nZ>&%#+)z369$|NC;+!$+`HtU60tJncr}xzq<#jF{tS3H0%8M#*~8-rF0{* z^OG-GY{bHKy36)Zc%pDjMN@L6kN|tOl&J0)hByB@PLw6-0W!!cP6VGVlPa={Cc<#1 zwgCHND!YQ;wr(E8^;(bFJohyxs}NcG-i= z?m6UYp1?wBwS6)9A8=7yJ4TKaBU8-c;t@|`!|HXSt5vc$?;#Yjz~B7IYZC)(AFbBQ zxq`nhR9z9@XPU(ivZKsGb3Cq@;IYnmDYZq`~dPl#W~Z;Vr%f};cOvII|8DCLqT zD{JE4bzC*5=CL))#H&^}ba!gZh`$R{Y)UVggJu<7+g-^9Mwih!eeI%%dbnjv4VGsu zJb0Iq*t;;fspIP`P^P?XgOE26yd|Ix+-093bNr)Q{KHY7(Yg-p9g>|JBzuQ~{Ub$f zt`1-LPX<}VEGJE>;&4Ek!-RW`<~Zt>HKlTvHPpo%%PE@jZYVVEKcF7z>1m)IiyHA` z7?venNfb(|n(1R$m}AXy!qps%fa`uK4-j2%QOeHEnz zVu4hMJ7Zg9XH~WLw5z373}eYX8W;0%G6 z`kn;8OMy0}n1VncXGM;NY=eUiY!R^|Lu|vv7-HNg;ST;&?Qs6n(dg1?qqf`b(!lc-wvYJ^$+QQNX%xCdlPR?u*6{uTU1H+ZH{p2y29E z@(8e31KWpP`AOI%YS-U7EH{U$Q+u>k&5&u2@;5N)3dh}%YemA4Xs{KtF8*+xuP*+0 z9lB2Gpd>9;juZpelJsY~;E+YHb_cMWZ$@$myH3fHqJn?u`OQH9Pm6g2xo+yfy19cV zHqDR_Xn`+@T`Jhz^) za70qPeLo^&jWbW&qib1R!xiqjK|KF9ZP+MS*NDs&U|%oOVK)AS$)77I&`lJZ9sqi_ zR%g^X7_V(=_Bka7SK*BsCn?_DNn`a?m*xim@-?k{gRS*0X}sz$EbmdKV(oKrt9c%; z5uQ3nN+Qyk&hWUoSGenJj2msVutbR)^?tAY;$J6GxI>)OUC+7r>5%wvH{A8wEx^Q< z*yeO5N6ToCy45&d;AoVN=Wrg+auwTl&m2XY676H(!r3dlEF39i^H+(@;%0uL)Sxsy zpa-l>m}7H!ess!I+A~(?V{cj8RTI@Za^hqk2iu*yM}#%E-TOD|_fenq#ik0~6(7+Z zKlhz)X(t`7y*cq7LJqySZ2dk?tIU3We02Cf?z^FrYJEX%gCrKw=KNfPH^+u2#E?*> zqz%~J&V4W4jWCjDl~c(pauA>3Be>)X8_9ZzlG zK z?{Bs_$*hhb}kgA1M~oE9q_#@L6Gz*5+mad8>C1%%SU8-_zt}ZaxO@ZoiW- zT=I@K_}VJ9eqOhf3D=e}^)%v)Q7Z{md(2nzFbgOD`i|`u{rh76MMX5eo2iSTt%zz{ zE)4%Ne=Z)B(JZ)35c@D2bFU6Nuz+*Eopsi7tufE>j~i!FS+Rh$=fTa2xw4v4L!-}Y zXreculpF18(tpwC9rCR$D5yJW%UB)o@xJ0&3W-}NZ!!Fl>Fw_QD;9a;>_S%Gt9WG9 z7k?R#j+CFP5h_i__Vt7rP716U~NP-uWe457(_CESn(M(Z2BExTZC@=JY7Lb zogHmXnQ(!P`2tPQq#-IGw#_o8m2Y9?Bp<**UD;XjL-_&|-&p@s&3vp&W_BUbS16vW z{xfRC3~8(f*rS|NC#7!A=!b3A#(n5W{jUyd3l8+M`&Cqd-2koEB-jgBS}=FB=^9OD zUY=!REt4Um(RAVjV>s`$E#7>)s9e64DudKR+*;i55A{v?Gi{R_{s5XsQxUmCvXnp> zB|6hPRc%HVo+_j04!r3l3y;jr@550?tij<;!day0)`0k3`c;mhbt!T`M19*dh69U> zP;;X~Lf+%niX($-6t$e@Ufx{`Pew2gx`^O?5SmdM*W7cunj@`2TYTfUIJ zc8Rk_jje%>LQ`|3=OVJj<@k%+0>+|*lZCstgL_lAwb54=1fZ)Ec>v(mPPx%GY zO#Bi9+QMS?MY)Jmn^W~)Tloji_TjTVVx-uJhbSDyGQ7eO47NJ4KD-hIN#qPmrsEo0&VD98m-E#^PKpnitB#~32$o-1kBR2}~e zd)`Fl^~j?lBN{`Hh@byP`GNPm_WG*Z zx0==!CMUc??-(|pAw1W0hTa1l?=`|OTx+;uyQ+Y#SCpbq z*>|s29U=1G;h|8A&_jxigX*3qTxHHvv~S@W#JhWi(6n3tnbfseNnwYAVjs%I(mV-S zM`Z~eke{FgwNjM!DoNsf)@CIAdu&9?<(-9v-B*>U`8riUGI!E)iLVv+Ha7>VDkd{l zW&+qKYRW`04{q{AcEwUZS<)1F0+GS(!+~j8fG*Wa7V?47v&*b6d;o_||@)F-ycb8o0@STh4Dv!6POzX{hwiLF! za-C+u4Lm!z&TCmJB#2WC*S0l$D7uWTW+GJ4-QHgB^O@PIIzZm8w6L4uNokBNNj|*L z9_}`q*o(^=zslenT7h8{vEn8Oj&J@_8fCh2b4H}isy{btsMBY2I&c)RZW(KH?z%nF z&j|`6b}~m8uBa6;r4>o@gceD_O9`Z6yWhl~`<{&Tq>|ms1ZzX@tZB!;e@2qFpj(gs zU2r;Gj@GkE%x}^iT7#E=g3u$jsEopjk)3(4&DNkj*>SlueTI~9LyI+szj!IM!lQ+* zySv2hoKwn+Gb~TiQqYntGy^g7-mbKhT)skTBCa|#c^SV@^o@k-6o?gp?j{@Uf2wM< zu|={at;)a30>EYD%m-ORrOHR@QV0{u)49z*W&r)h@;u~QJL1&8UnblrJi$QjQasg> z?P>R{&*u=3P=KSgt-hIK(y=f0QopZ?E_zgJAne;sCr9cY&azq#&~eHriUZyusMrw^ zs3{t<^$FWly9G;oV5#O(=l+&^nMnIFA@aSK_(D`Fdwj}!eli}oN=ti=$z}WYN*dA7 zDW3V(-ut(E_DpvfI~zUf>d03w-b+^pq;~z?tC8mr_Ex=5;R; zck}!cxlhdleJ!v{`e)pr_2&uiQG`l=W7`RI-pDRt@T+N#&PZleUOX|QNx63qEUzUw zzYodt69FQ_LE54nZwPr@*p7ikFUos1O!N;bp`Ij%axsSYygYIS?ko0# z;YPjoNNYDdn$l9Q;-iPwE8m@2^oFn7-r|y9Q^QpaQFy5Qer-0 zViU#n*QCu|iG4L6*ramCZ3v6V!9>c96EZQ>6}Kl9r$l6Oo%?H)uUS72P{>WR5Mlr5 zr0+uE7c)}9sp2ogu`?>xGx+Mx4gF!ENQc~}>EEWh{*eBYPeH#;cP%0YV6mV~_9>VJ z7|1RnK{174oio@vmI_rXSeRCi_NPnk1x*00e|>sG@cQq9V zXHvt+#H&v5htY*!sLE!nmI9E?3x_-Q@kM=ZY};zMLgH6_gsRe%|4*l z`>%7-qmm7V1%Q}al}$JG)hnY%)a*3u*MX!^Ot_RX%o3`LrBa4zjX90WNLSkL2!ipv zG|R7lOARK+(>T;YXLpo@kg2D`5TlV$gVGKnQW=7U$ZibYS%zN7smoSjP=q#w(?%z%GMz`UPmddmZh?8wrv^BFBhGyU^oi zK2&Nr6Xyg}Qfw+moyh1Ulv9{|86~aNU92r@yz+Y1Q~Cu`)0YX>4GeDHlea<1$NFyZ zmhM03>nqIMP;hXxQpU#_sjh?yj}a90sx{ot0E+hdOlxd;zn_)t)0L`oN@b1mAUs`R zmWGb66`J?%h^&URu%n=<0to=QPPZQjiYndT+@E)7$(~4jK%CeGAK_JjxWK*6xUb)8 zTQx)7x`MP7?p2o`9bx2H4BE}$O!qBf3zI}O4g~9=P6vKT4HwK)k^=+yQkXSMy@wE}Xr#UD+5S>B<+JU1W zNq2cUBB5gv%#YM^PmyGGC<3J>-q7@qCh%E5VzUBa%!jJ!@kEEP!2%W0CSMDla9FYX z`Z>U`+R?mo%-bb~d)_v9R~BOPmXV>XLeApzcD8b?tb^VRYD)}mb^VXn)VHc3S4^_H z3jRrCvkLh>;(XIZihS6rpLP&h3h!et4Vl4+#DmNdKO>TJSdc7}GL_{jUB3(g7i;)} zzXsQFsGIiVYU1PbIEZ8M$Q0iR&WDa*yXFqZp}GK3cmW<3323-53FZ%9(LWUlKXj0> zu97?RD0c2eLLMQkG&0y#u&S1*Gi*_7mJL^nIve$FzjhxDaqIJV*>xgi*?#AXZX?q_{wq&J6152A(mOye>}=|^YXvDSHK)A;O1p&1jXF>1n(k~4iXPE z%QK3_OAN9RV8fZZe>nth(ozrj(ySnIW5yDNqYpo&k08+EDr>Xv z+ROvtZBzc##Dm~%Z{^i7VAvP`0L8P8rfcJV35R&;5JO0pK7NGd8`mvPaoddO4VR-< z@8VtI+D9M$DuMJE)a>$!dhe}Ke+8EZr-nhU7!f{k7sH{aW~E6ic|Z5%)HK2K9N#X% z?H>PVF1;l%P>GKZTTMK+q%jbVg0QR?S7$`6Tjh|oFql$jmeRUrC%s_k+h69;QG|?S z2E!!UV~!Y@+3Cn)rAV3T0a-`d@9WHIcJ3|(+VKSXD0VH+x>ZJEM-54e#i;q~;j2Gc z`qS5ePjSn^M6I#lZ;t$^c1+h%%V8s}OHUbXu%9)xS|CSuh?eMAk&k?MjGqsZz)j?o zLz$Qk!u7Wt5DPUGN3?--u5)S!QR8pQzqc(U?|>!4m;v-WD))TrMtJ3Q0*+J(u$E@SKeT@`BkbLQXnyC9%<)?N!#(`2h)rmK;RQVxhKEwjSkzC(x@HiE- z%!%gPfmPME_{h}{G`h3vBk*UI7 z=WEDvV)?ok^Gcl*)|N^2rBrtt())Vr!Cx=4M6Vvlumy(Hrvfum%8H+g1AtMKkIYNh zLY*&%=GBL=YtPwEF3904Iv2Veuu?lxz`~*Y_02@8tBWEbg$#Ve!!+JaC9DQ@;V08H zjW(-(MTGDjacN0Bjz4E(UxSgVX3(@_(RyoRLM8kx`l+jst0&w^>4%^y_ zn#kVmsEtn{m4^+e-zv9m&tr=UT#xQcZ3!o%bQD^{o6{gJa2W7+LVrOwshmEv&zS1r z*9Y9Xy8n>t%@@}qq0yaH#?-2e`bqcCqJRE7RI1enAf2}?Sg7t$j2HCN`nCc6Pz76# z!NAMz4wF~Xxd-9|TQ)0Io}~(hMx`}`+$Jm@OFB)ySFA zW-^S-_}j3Pq^4@iYHe&x{lM&J&}Pc%lmiY>*G4o<@)a=jr?ANIRVA;!=Q~pCO4RVm zT={!m)hkUoaUbPbVEjnS&L6+^T!M`pJck_P290>U8qH99QH_klBIX%npws$mk3(dM zkk7UZ|H7Zi=8w6K!piGPXrRvV3Pq26#`B>Q17lY7o&Fn#%gox2MU4&L+y!N!}GPbb-kte|}CwO1iFr%%W{T1Po znfix!25VA%Dti}*Z|k?kRtV!(^m7*ylBf5ut9gD;jldgXpZ3JhW<$fI#qCLi8p+@O ztw}q&NZ(f0tw$nsWnDzaM(p{Ky|Z{Fn}0qT7HV7hvh|q|tx1%sHqI*c0r>U&$2Q7Gm@`F^ zeLrRAbmhha=e334?%tR`s&c>j42n1d7AILGZwc-cT5;a8!ig~YS^zHoZph!m!~v%4 zIqdkM0AB>!Q*(e4StMbA5bilvgG6BziZ27@OBph7$60axUW{hsXjcUJjEf9fPdQmj2ni+5lK-E6uwplo)D4rSd>cQ>4 zhgmIMG3^VaKs}9h)vO=cf58+gWgBw$g)*Ke0%Ha62UgVo&h^FmD60qQ1;o3YpG)=j z8pqA@uF6ob6-%79iXS|K4ctx05j9@KxE08%hLx2yTFDp#sw#YZ=e-=)Tn^xdmEVc% zX1;b6B{;8%&iN+}G1qq(23oD^;Im;GDkGS_*Wk(LFMV3DUh zK4wSOOxEJJ_u3gEZcwvGnsLu#_O}uBhQZ1=ZT;cb*e}iIfdm(6*hMtkwS>U(U8=VP zKWq+Iwr6}+?O)LI;_lq1=y&#iX1$YmuBaV*aRc9dZ>0R-!+NkiooJo?kq* z(JnhL^*s@0S%9d<^C>mGx~AC;2detoTChrxeEKT{Tua|-BwKFHU?#aU@14<7Nbroe zRv2qS;L^1lRRF$u&T*pLyNP(h2P5L$iVcM56kis?`W|#GlsH{Hhr3%K*yOS>d2-T5 zUrmYqWTJ<00Pj(&8$MtsDq z(fA=4w-S`d@$RTNw=*UiQn9wD)I7sfsdt=CSfM*&%zVw|mkis>< zy>o)v3q3iUg?esV_snH;@0Bxk2USpHBCJ?n{#}#krEqut^SjV{{lWY8kj=ZW;_Roi z=|1x52wawoLRBWDQrcsA^U~5oj`oJVzGGp3UEB0mBb$bKV&nYsXS2NYmHX>&cFPPk zhJM7=1&j+!4o_RiLD@A?t3q=ErwM9}3Uio0TP)CUQF_A1O zGDg*O8DbkU)~7h4cnAh)`C$z#qVu1b#aXvChSent_MGi@TNH_Yk_t9nHO5);v9_LA zn~#;W+y>;)(U*mt>}G4q7XNX)&g9(_xb4QoVHy4NZ2#qx;{lx!TYK#5>}E?Md62_F zEx%MJpd6GbM9k`!vh?JN7D?9EU)ZS%$+sxOdSSeLBg1;AGX6o=pbSHoBT?`6;-Bbe z)qTg%DVXfzQ2lbvIFOHqYRo>xY<`B4C`c;*X3mT~qpv#n4^- z5??>D^zw(tSCOgMc1 zfSZ0H*N?&nGL-rlwEZz!i{a#;Epx!*cPqtb5R{6Jiv_!-W-$v6ab;t>A6Ti;wMQK` z$<-vk?{Rc0U1aDmZuP+Mo1vcWy_Q9?Ro8lolx00!wT)d_N(&x9k6(wEgGj4`&_27QH_F=8 z6p(fN9Gm2c%c&mutjv`~=9fR@W|8mC7NF0OCov^enFKRbzIH|V#2V*j^H$z@~;r$ zVdJ#1Tn-U-#&(o~AcCIZ7jdIwmAsp$Ac+Q$UeTuLB(|4Ra%o?c<18C`%N*d~BQCr7 zVyB)Y1{Gq=1Z;q{T@+Xen&kX8n9bOejGg@s&Ey%WH*@5>`BSjX{oKv&(6MAd z-J9skJwu|9SWY0CqsZSWS^muYR|XYa!?!+IUG>X`3_Xq*EcczaQ1(2TmUF_?d0TF>$qc4gTcY|mf%UpE*B6VBg7H<6Om5;77ki_|5Yx%z6DagAT0VDadK zzlFy(c_dGD#1MOg%y@B)AIHu`tCgr5(mhPFy_x(66zEuplK%aV4{{d$Ux5Nr|NlS% zcTHzyQ%B4HH}fOmX>M=n@n6J1xrUN6kruWvXW)#9r^tDkV%}_-oPpooq9dU?{16uc zP*+J^ueOVvh<3UxdyzDifT;Bi%j<%&5m6nB8kad1MA~?WM=nBBRdn>rdIho!w>q8s zF~9R_I^Z$y_H8Patq+nt@mwxXj3R`Z^{0zm_%k)Dxp?skw!Y?W3}XFA1NLApY}nPJ zA(ZtBYnf2D7{kkzP&5- zgsW2zT*nj2$=!qe+UFbCR$r%Xx8kiFCs3RO4YaHxR?e9F_Mr=Wuc(3MD^#Do1}Fus zfBmZ#^!KYbH^Fa?P+o1CjEBD10>-wm{Sgn+^s+#DDYFrz0qM@u1|tp+CeNVmcaCb` z$IZ>nzOsQ||C$FQxjjM5kKZUeb8=f`FBYh#-Kw{d8UJ<{%$x1$-59UBi@2%d_Zu*j z&1Oo7MySi~Q)|f=N40C<507Ejnd*h(;AorsjEI2`k-6Ec5?D1!KAm=tmJ19_?YiK) zU@7%H3HHv^+t{G7&g{B0i{){s!)JPC!gzE-r+7MUzH{zVE{YPMG{H})22fQPoe1qVBMZ$l$z)%zn3X97BBm}qyOMOs;J^g_nC=iYilDKonmAOXT z$1k~fpn43Fka&v=1S8Q7)sM#Pe02?K+RE~@Lk?g38X;}Rg+&lQg-aI=VkaGhrb3Ru zJrLEPg<|709y*=g+-c@d9O-&TpPQ_db>|+TOTc)Ab`3`(pi~&ZO#Et4V&owY;V!4& zq(RVnP(0pvU38!Cx_W4(p&Ye?ffq#1LOO^UWGXGxpJ!t#4hgjVn%zmHZdV6HZ5H!N z^{0-UG><_}UpzPZIOmguLXNM9R*bibqSTKTfj?J~RnPa03$yZT8+?Fki*aS~UQK9Q zONcTDeGX+Vl2SO2d1aRecVf4eZ0YYwrdythpKp9}WFUYnF-vctIhx3h556F}p~t$x zGr8ffWOp;jaPL3z%~Ha0?bELr&&HJ#HcfPFS{e>_qV97{+YcjJMuRl|X7fCrR&Kh_ zxC^L!VhZzb{qXNyaEtrJDLdh+d&6aQKz($8oi8F;S=l(biSq3uJg*I~;VSUST&Xk( zuvH%ruagVR0uiNoT{!LZrSbPoiA6a7v1 z$l_`uGU($BKqqA<^x;3=K0DKR)Lgm@e0c?9eSyki6Q_CM(kv=84c;XlBp>`1frKoH zjK__XPknbGgB5cA;0foGXB zkH6(-+J}a`RFK=40qN9|A6r~m&4W=UwvqkWoL=?lNFA*Agj~a<97OY#yTn<)%Z2Pk zv-m9(A9^g@<~dM0!az*Ke`+|H2_8cuiB!`v&paA#$ag>HIh-d@)|P?gqe*x%5-NY; zg#2u|s^u4F6^C;A^Fq2J5gyl7kV}Nsv1OX0aP-;9@0MI)>&1#V8rHW5pGIpmlf5y{ zEVu?^&#y#iIZr6xN3QVDq^Ky(*`-!KtrD+I5a2qq*=w|lzy(oeExX32 z62PWb8Xy1Q^|4DCu_8D^FRk`IsKut=$K1D_Fo>ODbLQhQYG^Dq!dc%&-99Lm9G^u| z!4WV}sJ&|S&>d>I{SnYpQ|{%LMs{&tVq;eyZ}ubf<=?{Bu^(min%IZ_JlN^CVEPIy zb;ZcCtZ6w{jookVAqLoydWio{`(fSSFdo`3j`G^1InUAV6f5+~^t}Z)bA2vP z$D9=L7E^jrYk~?S7|nEmjAkb4zzgOY$_MQlN-sP$jMrF3ZzCA*N4eOginQ*!NP5#y z(8wxP<1|lnVzb&FmP2|++7xDHqtV_C6mk&tU}4e3OjU}BDqCNPR-k12TY~sAROt;J z?ID|Ghi_$+@Sw(tG+H{oME!i+K9?&D@cwSZhP)dGlwb7@^`Fqg`4RjA`!Doz|CijJ z@c((}{WtK|;NOV;!`9EB$L;LgY{eA00d(5bkdTHjIazA4Br!;c@6gTk2;Dh~DIv&; zRs$B4v5PQOTK4DC)n9R>7;9T`ndNNMT8LJA*0mf>&i(2y&(FpHE*a~4(Sp)xqTFok;!YN0U0K7Dtlk>nIsRfqGt3x!K zoEst0C3!#i6{jriNHuU2r>xJ^D{$Gh#ugHeFZ_R*P=5N%hIbK0g zR>UOA*QMbBpKHDOa%-%xFO5Svz7g(KQ}4IRW4DwI`-$t&S6rJT>HMQQ zLw}s>r4jiY#&2$x#rAZq9)oBBoJtqR$B~_#{7U{|h3$NeYMW=+?a_&D?%lqFv$NVpO73&i1ysTupNLvS?BH8;v%9#=gQ_ zD7*@^g{MN+b?Gwa>Efc?KScP!5VI6N*mSVA@v|m%{_+A&<-BApXDlCe#kWFQh*~hm zj`*e)>$*A8uj6Tt9cRGv5!o#pGz-0{Z>%}{=#6Zt!8e)RTw zu8rq(4KKf}4^QZ8bL3h_dqdsV)Yuv`@Vi##YA8by=+)wz3FaR|IdbW66h^m;L$vlHlXLcBJaek+-@=!m>elwz^D|nC=O} z+HKl4+OkyerWx#E0S~Ks`{>c*CIST{=0^Xzm{FWA~qIw@V%` zpQyoozhU%jU#}Tj)Fx$9j3+)|;^d5^d1=&Z>3`-vvzcC8$cD*HFM;{Sel?r~aMEbo zG53ZlZ!(4e$;1RiIJb%&cP)*Q>DM;f_zBk~P}Q_rT=(I2PtRme$y+lc{V;b?=kp$} z>nawoPf_d3*~LZ|L1wN^BtYnO?_aKjCB>^5P08FKRh6*PT~#))dhF`;^uND6(Xhz0 z-OglupgfIgwd&B30yZlMfGfn~7lVN#!}x(WCQ;1TV-$hpUePSrQZ4*TC`I;f3Kl(e z#n?4DnwHgw%Nu9qu}7p=0tn>dppXI?W3Rxcx3QJFIRuF`>EkT}*f&`}lC4?L)#QS$ zXVbegT>S8i-fZT237!s%MRI;`)+y(Z^}t+p1^<9I-F#-0t=1UKbFS|s8Aio|jE z8LDWkN8gZ}y_|DV9O*eoELO%k4hJ%bY~(3QnHk_FpdDi$U^OnraYeI$1y z?B8lsH>*zOO{!kU*YsVd(N0;lK&Ko~R=%yjK=OPT4=Ly5$3WtUF_#gVV~8JH`^L~% zsv(&@Pyqk<09-pYKuEOGuxr}Pf=;7LDDgJSthrxB8x)M{WW}PrRk0o+55WXkKj9~T zss=E#C!{#la1uKGjg~4^vXa2j{rH>Pm#Z@lnpcKnnEctSSkaf-Wi^l}BS=&w1ZdDg zVYVs37Kn$P>Y~|Vw_wW;C=iEg@|~UHR5{bg?9aCzd1(trK|f_lO}tV6nHAxUcWEKo zoHxG_iO0WgsYc^>sko9ndm7Ma%*vxHox(OPKJWOT*B>zuw`M7yi?h8ZnN*=Tdh*9i zXl^B$%RirE3eNT6=Uhe~Tu_@MuRkXn#@}eV6c(_LBz@aNO8bh7dmAYPv*WHq!Xu5q z028I*?bB!Wu7Vyto=B{7mkX1V`!1^C@j!mZM0mGrrWn|dzRu{0V;Y?e%z!5nvtdM; z_-#uv-Y58n3j00zU(emt#i&8PfAYq~D5{Q~aLL7}NT2OWpKY}YpKPD3A5fb@qh=&W zNASF8jltYxeD4U@3H`7&D*z+^bsAEP5hEYW8TiSdLO;&!#IwC-Rxm#}OTph*=r5+p z*RwFbF>@%^H+0cGl0uhVgQqqHf3-ZfUF(va@Xw)YR={s2Sg=9hJ5pbP)mgn_Kr}2U z)5s@T#spOV?T@pNO9WiO;*bWbtG3YPfE+kZ0UBh17jeRe&JFxJ9u>O`EZt=(wox)u zFyYPq5r@wtsAe$xA9kr~2MciO%$nlljC*WQ=Fq^^~LFpYH)u z6H4KgJ2>vI zgSX=zeHBaQa+{|h#1=R@l?Yvt(0^{l8B3Pv=)^zsKDK%^z1u0eG&Ezl2LmqoUDi%u zjO)+9H&w^W+6Yk{gXH0%7Kbw1CtZa$iWE>p-^>4-#w@rYpP)!y7@d}NcY6B^utL_` z#2X=g5_K{M=j8JI62Tk21ft6ZMnbOF8fN{+{3SBFceJyNe6V}s=;~r`I_~_EK|#9@0I^yX5APd^)Sq5JMZ zv!_*A>JolE(*BwE!C#LUqB)p>K}eEA-7I&yjr)DdWz`;5f|46$#9eE77FP3$A>3S& z(KeK{wisDS^$+G^GwTxb!W_*@oa>w+at}_bUkbd}M_7*174@mEsSIJ~5qnZt zudED3#60Qc%k+ijReH=-dJ}1m`*xG!LJUZTGH)V57Sx__jRLn^Qt`F(;8x=TINC;6 z4SzpQRuGP+?XLAnteL$NFujk=l)abR*JQ%J=t_0+kAxW=g`$GtT}zOCOD>Ji$350w zEQAuDWoYpQd3f}jRx21Nj7=csD*w=>e|xdhV6e5(x>!MBPn%&?c}v!nixdCI-!$*d zt=@z;UZkZD9SlZDiguW)&YOH$4TZmYFgZ3XGBdB8j$?h6iAIn`a1w@e-1$m{51J#vW>mdpbrbP&+fPUSYtepC&rGCy3`;)P9&4u0`hHlfI5F=vbb4 zzfE|a)&p%++Eq5sFMZufB z(3KXL5vj#3Q?{a} z<;9N~P>Q6$QzU~(A6Af+!>g>ITfhrb{5R{DHTb;V{4CuG-VWfL@X~pMKL7?jK&p@} z^oTQJ%%1!&{RC|O>PzH|PqE~{ay*Qv}1B2j{0LcL@YWVvaC8Zq) zhHU(HWYutx6>3!d^b&#S5|M1Rh)sh~5Ry_;05=@aP{cS)&>K#uo^z>Rshp8?2rrPj zBrr9092pKiTNqLsIdP6JSx#?RgyCgnoPT>DBoVl5@oD1$BiNGE01r}8on|I4DdKNY z8c1yUu9!*bvfRdnTz3SU8_d~MP%?%R5UNtSMH>*fFGmn@P+ehcG6v@#l7s>4DIpI! zXAXF`W56&>g~pdb86FMvo5fvd4fBiyGGD?5jewj=N0?0zy#VLEfKqU|K^`F{ z=*5SBg=O^)@K1E4xZ6S^&QlW??Uo}#Y#mt^VA`8Gfhm8IE2A}jEYdsBC^cG)2#9nK^ zdeGn+6(MBkOLX3#vDnJ@LP!oFy*gkM-$`k=!7vEk+ zp~YPw42jvLZiaH$b!53zj2NMtYI9h+qf6Ag3^}Re3&x;e{~d$*9L#Mh!99{0w{*HM zBOhE(T$+$i#P&2=y$)mQo%m!8F4~P0Bz) zt5^4j#wad>R5jUh@9l+Aw#G~%v%Lb1>tkTl^``<>N$ zTSq2l+Ex)ZtvmF%7}43?AaHMX)vyWTxoOS?H(DQiNmt$AZhbtuE8}L}uoo9l<7rJ6 z)qgq*YhNuoJ~Z>#amWAiY$@14zMMa*e7tkF2I+P7%Oo3~j5@s{4-zO0vOSMLu(mg! zok7^1pu{LgYIMMAF!D!;L|KB?BGJA@YE&?u5r8HP?L5J&fr2+M5pW>kkB~75NCN(0 zV)i~-pfuL&Zuf37W9gIH*LHyJ=x?K8l@97pJ|LVI*Hk3#mB4TOF|jh8m0)tF^~r*= zeLlbC*s7IcNpxT9zLu<_bLP(?f58MsjIsTMA447>^6Do^DJu3dWA_GyDr#`$Za)ix z{IOrFoi0EsT-9m)Eo&rItU#*i1BpwF|9sIJYFYd2ut3&2CR9xY%U19!9TVuQ3#+LZ zGB9SaNWj#pq884t!?sM+s8CfwOmYr9q;#T4Hf&djwoC-w$kC#(*Osm=iYwzAT8z3R zt-9c1Q*YTa+8bN2C9Z1yD;}u)Qlm~-p-)+r1int(=W|k}!4Rl$LORk<-X1LfRT+9m zsv!UX=LxR<{p+YM9b;#WxTR?hvVB-)$AcMYvlLO=U;OzDw5?5xBYJm{q{9VBr#&!P zh3~KU`z2_HOY0RjV`rOa{5j;n-qIwN?O#1XdCifx`-{ z!-|YnsZ}p%A=ENCLSRDWN|}|QR;WSq91%cQSd<+{D;`iqykQ$j&xNul^`B?8U)Kx( zCTHEmJ+KcFB`k54<$-G=IU&RgE)b2;neG5{!S=YIoPxDz;yJ#ifZh>FcS9ji5BcDz zE75W#bSjeJjdKQ?r@xM}cbWm$A9=Pu^(0SiL5|MXhS{;fYr8g{??w2z4R(bGJh<8PWw zH+*K%JRMtq*zUvlP71vcfEJ13$xFbWK30spMwMvF$^uyZSZ?$UHUox1_8%LB?1{g= zV-fqwh$?IR1$=(ON>oaV?;N-&DYt*MTQcCJ?ft%)%c!iCZal36)T9VLDhng;(47wN zM-;mL2pj^NP>?amx)T<5buB1-D*zs&lztV?aiEBQij?V!U0JmpP#5e^GZJRUrHV~T z9P{xI!F&xrw1yr@SDqWdjDS#mt|7F!C6pYP8KWdDn7&(!#`NQmTzDOB9JA?4t_UAH z$a>TSGV9>8&=N!@M^xE!U;sics*Rb?y+999$o7?U2bqtlj|s@m^M64Kk5%WVLJpQE z-XjxaH^N>Z@9CSV>C02mgf-OKoz>Z$#lhx_ojoHKn?MzpZ~Mn1HBrmjJb_T=nH_%b zDbKGFX!JkYW<{UJbt;2^TvhN`f7o5*Hpe;;gapaykS>1Vq>lN@Iebnh+;Kcc84RFg z=0(NAySI0ztZ!N+mCbuE`tV&gG_UFIWNj>xPPersb3O7r94)(>89j$$3ngsFH-Gu% z9pLS%dq@6)1yIJnaRLE0F(^|- z@mSYcq;3pG1HQ&7&KpOWVB~-8uC5NfkAOR_P>6~qkduV}cE>Rv<0GHY#h-Qqicer; z(8lN^2{Z_ztPxtQk!Z{ff~BEiS%#lfu*fW2;H0vuX6kprOp{u#_wcQk3CP`ojp{iy zt#RtdPvVi5Ul!Qt{TrXR=o8j06qgq4{I`gl<~YWG(Is?19{A%*Tze2X)_vgTbhYGq zRk>9ix-Zy`sdH-j3mU7)h+`2XJG1?|0NdGlGJ{NWH>S_}w$6eV5(8ox4y6h+C{{G{IEDUn=_C3h4* zB+n;;me5HF8R>u$rZ9HCTJ*k0-1H?ELnIWmgs*A6#bCsGQ1pRAEzk@B@1U#M`r8Wz zBTydJYHxU$VR^lYyS4NH2iWaQnf%yp!CPwuRFYF%>2+^tCq%3E!=#1XT4&sRD}L9H zkNrE9^V=0%fwT+sR%^b$7(%3_C#OVA*ThWMdHXeB0FR^-a?c9PE22ePvco(5CCYsM zq>nBY%6uZCyRc4Wu7qVQK5 zkjF+AXj4RVmGA8n9==Z@LRN3<_=A|MkWKrMLdcAZLW|jm0j5BW!5bsgX(dFH$4*9( zLeOzU6N+<#XjH&Om!rgs_;C5H+6xU+VGVy-~O6sLOT%{gXAM{GTrlwWL>&Cs>Yme3aK;ECJlYXR|^6Dj72}KE?Cq7&p1suHusH}MaWEs3MnX$ z`6=EY<x#XbknN3c-?gawDU{z=(=j0g`d2WESK;STWpQi{vY>+d3gVjR(T^!fZci0Z~^Nu@;ON1R#s)LGHE2qMvOciD#S^R+LfHduoRcj zm8waE_lDLz7wfDRSuDCJ3*1|bN(JsJ8pHsN#FxS*wX8%ud6IRBAUx41<}zCzMjduL zkj-CT)O1bbH9|$~?c_q)5i;rqc-)MftrV1A%2SfX-&10=v}Gw-FMeW7&n*V~75|^kERUG_e$h%iOmdu|WaImq~w#`e^p| zD-AM7lF$xCg$hS@U;YjcXvK!Vi^K75i??hK7`em5PydC7(h2#|?0w)(B-BPL+=UMF z5iAj0x`~vYiVtA!o1J;lFA`Z9lK=b>zR8y{;&Xt%hYTa#udrR}tfM}*U7`eITa>aq zGk3a~MJ%2jm7&dETE%^>751TCQt2tu1}$zv;E`wumhaN0`e;@* z$vq^K`S=XFx3KY+X^|*ZF_tMjm=b>=KSI{ruDehY(hP0;6HbvyYrkZ^D>u$-t3H z14b*rupLt+M1vDM{qok!n8iQgL02oU`T^YeCYyhpH_iPqsnebs7;;6k9w95($| zG}-MN?;p+CCwYcUMx0;2w0?9`{(E(#e}gL}Xr}M*|H+H^FI=fOepw`W_~CEcZL0Mo z8sr*{L`uEICS+8qGk(DZQc`JnID9nPjw-menoX8=&48>=Y)o0wEP4PbZa0?OfES7g zc3Vlgd>!KS%v3feE~akx_rv#L*pY_?3`x|ah9PTj{=RE!l#*X1>*qvQ$*{hPvMCu>-YVahG@ zj~6tjR-%5kT>K?glvZq9j1emSB&*8Or(j*@V$Z1THBFm z=(wt18Ak=3W#t3&&{^ccyFWB@$xa*1Aga1c3TZsKw=!sLZ(fJ6mVHE$`ReHy&8X62 z!B=JoGN=X&H&KNSHFe7-jm7TEm}XBb=pl`G0+d0<0u~4VqBL4T7mkylD36^ppF&@P ziL*LX5qeXCfQle~XmxavEYg{`G+{KK%>nNIzR{+EnD5Y=lFq`6ZEbH#!jco`xP|5; zxk=9iGRjIy`K`BU1bWeKvos$anSwy^rN5-o%)N1}JtMdz93sW438Uh&OT2#;i=Oot z&fa$#-%L~XXc!-Pz-znUZh0v@!Vb&}MhfIx{h*K0_W+T&g8&Cf-v-G=n-`WaF#SnX zV!c48;mJ@|~E^xxhCuBiC>vfC?n4V-4Crie`@e z*?W2(-1sY|$*z+9QG`w>cU5sVcyPY}FTuDY`Lp<=;0UK3Q}zVO@3~E0!~hw#uN|Og zbE;`c;b8zLkOG@XNB16NX5))Tnbk14pJ70;@-rkytHwuSm#FCFLxT3;{(sL*UOP4l_ zO`4YN5{VZ-w7^yf=C^a%Coqm`n5?`!*`_2Moc=pKo-nGoM0S1Fg0i`wyBNUUJOkHDHA7+=4Zc}db_5S@_5!#f8@K_1{J)OH)o}mq^?gTl`bQO zT#OY)%hV{XRg6L3%g#LWPo+G|NY+f3MB_`1xKX7Cp3_bnfqCZeq|?P!ma$GFO+416 z;yGH0Hbc$esCZ}))1CgHbjR(48X>!WaT|)xPn|f=mI*PhDlR5T56AOZ8VeRP64;ik z-})G9rnG~S9)}qrUwbV!KNn3(*;4i@yb<0SCASuHq^%APg%T@_;xffo%90@7N^vJz zg_+jnNvLvX;v{UAo=-Tr5;mqEQjU%C#6*&nu@2N*hsDw*vF58gQemamGCw%j-H# z2QE$+$u=b)o|{Zh4$=$cc!_pytSjXP?tLtm23}sV&P#cU^2{H#q?@co1#TcQ@F#}> zcl6z@ZSkZc7xPYeL~M@$pC<-VAz##>mJk^D_J82vtCAqvQzUJ^Oro0yv=eM)n1Ga-J z{yhwCQ;@tlrjL)1Fs;^jO8#z4vzVt<1hl7KK#XRMcrW6zJ`!tX zN&<1J4R%o56hO{pw|**~TYXY0<4D79kS9HMk7mp(W=*Og%&DefiHS;0-$Qhe+@7Qz z7jno9gGrMH0D>@r(F(-@wTYi~Ua`jahXq>RK?JhL(5A`%Ve!@`%efVuf#&If>)N@X z8RG}j0`c?>CmaF2$2XUcrEtK8x)upzH&yPE6F%IYZ%C- zP(qg#6bhK7`t_qPm5${cHuUw#TwpeD6l7S!CtYDBMKpmkou^hshVe8o@lA>q zSDQ_cBqrd(4l(GQbz>a(c9erN7LJi~FXJMH2y;iKlgE}WGH@{#fWCCw{;f;mtB#=2 z#+-mL@{nF@MUJ_aVgV&w9~UQ(nSs?N#366$uYSbr)#P$@5Kyr;fS9aWy4hN0#Q4+u zk-%0f9tNF@Q(NEnHgVK4D|@IDCVQ$jz6NNc;a)?IG*&$A4N7kr!4wN8TF+m7bf&4K ziF|aH)*=*TWnpEuj%Zk5VQWB0>Q?=e?oU0eSe=Rt(}o$}S{GEG&82UQOJyKFW7uAN zUe`*Kp@eNkkwpCLT}D3%;1NySFqHHZTuD7PvP8Bcmv92wv#e-wOt>vL} zf$N?aYB#E!LjP8Y!G*H6S-ST2o5=h?R?a#q%p3<=YHHw6MPNh>(f&b9v#zwNmSS!V z(R_WYBe#(vPe-9LnZ;^zVZr0o+=njMRi*b+;FYM{LoVnq_ zwk{F{@kLMk6`76qb~Rr5Fo_*5S=M}?0tt8B>~=d?yJBbA6p8OH4E{G2?e6hKA@_U@ z3W-@dv+eO~llVR6#E^0PQS$vqnE3rA_wm7?VX+a!px->oGZTqlsq>Uc*)egUhYV90 zTjlkp%C_NSrwU&4l>NyOFF4)t81GTW!751yZS+#AtUqmr=lDk744Cby-BwN2fw&o1 zq7jA7{bH8x&6aN7FS^`p)B2Zmq@Wg5^DLB}AZW$;9L8EAVx$oDb38w&LP6jWHwXWpO> zedL=GOZ1>VH;*XK8*yY9!y(}B)|poC@Iv;{<`-6?LmVBhAVw2aek0B3Q_T)zr%r(Y z*)kBxutPb%sH|N~ul8$Cn{`B*3^Movimo4F!v zOgott4bk0g3u3?bRq~^6#D*=p_M-1b`klUPu>T$y$n=%dc65sPl6s-fr?pa;?V5Nr z#%tO(hHL6Ja^>)G1$JPjtEV0W>c;vkje zFa8WQ-pI2+n!$WH;rcuA9x1|%I>VHfNp8FJ4VD-XMKPLUBhTQ{$Et&V{r0D%dM z&JBN#!Q+;5eL<@w1sx!7D{g!||5#(|9gZF#!ai5P?hSKJE2`2Q{OsrBxGbGFjKdpYoF!;gJJ`;j~c0ZLwZ@An{w zbzzzw)&m^h4zHTLVh0@8!r#Bx6zKNnX^($4T8&y>daehaQr{GMnXaSvB;RIQ4`a6> z9?!6-i6d0x$yDT`(-zQb36W})VkIlW5iW$|t_1;;)P%&vOEHTm1TL5ttk7wyyGLX6Uz0;rwSpWya0xY~GMh2=KngIj}5o3~EV z0xaCkg*K5-!f}krpLtXi>ef^ap5b(%j2~&CW(<^F4@uSx z*#xXKG|l>YG2gZPVCFL}(!1oizac9!;-N3x*kXd)W5-;3tts6qI%lxfFzrz_37q#XsgNftM(B{zm=qi8?4Vi_*UOu3mF-kaNf_ zo$7e!INWjCC%zQnOJ9QMWs13vND*__v1Qs}$Gbze?xeGD3T&5_l85{^*Pjm}`X<$c zgeZfn-kNO=e|Jh7nfrE?r52zoq`x2=MBs^A(I8P4JUdV4L@B!Rd&^uZ+tL2sclny9 zpN7@I8v@lm*A7?O>iJ*~NG0GWQZB$y4n`Yw0ce14`U#N+yQk%dk)pLHIo1wO8cDD4 z!&_wN=h$?AI|HNi0dS5Z-l55Ik5|ODf74|CM)F2lKhbRQ?+QToM(cdwDL8#!k*3q5 z`~1~B180x*3En&}dqJ!`9(&-EsAHfO2KKT?9K3D${@UpV4@aT76&_MK;7rF`O{*xP zE0^5*=RBMMrJsuZi`!qc5}g;zX8J)fgV@O{R)+9}_2~R%PJBr`zPZggGnx^^&se?3 z9q=AdW?Tz0;_A=2Ue9PznLTV|rw~%Gd?EN}1kAl7>lhtIG=Fm&mcQKx!X6c^Ra>+bOd*pG z`ZnRt@n7cHt(hy;%C!JAW4RJk>sv>csDCnCBM1aBL}9tQapAqbKvxk0_Ma|)7|j3E zZVj#pth4xra9|79g0k_Dm|t?RaHT(7`SjKqqzy-uO_lz~6j?Ha9jH_qKvr|*8l~7B zah$!G{=~}D=FkEA0qf)e^6o@=B4>FesoOZ`(+}wbOHIeAEr2gn)GoT86BeT~??ojEGtgFZFwwT1)yRH4T7ueP~TR&qNA{ zO3PJf<>`9kD81Jw`3iKA#0p^1nxE;Niruo$6i@K-ORBqIsCE&%sslo{WrcmP0}KD& zsImSM8zf1vQt3^6x&)_D5(o3^^bzev?Cl&Bf#srnF)D#lR;0r<>Wp#}!%9}LM-2$t zC4m;L^k~Zl#)~=BTwTqI35+oaTdWI>h>{tKe0kQ2BIw=V`i2Bc;<)TF>OohvL|=Mz zYU-EYqBO2=S?Mi~BA|<7xwx2bSXuiki2yD09omo^jc$)FQGBL=c4K zO1G!fr7npHE=ui%6JGE>-Su`pwn5vCZTV}h(}NI4DBbtJoQ~nOe2#_66j84X0QGgKFD)VL(w{R1B zK-Kd&b0|lx#*XF!ilV66=Q`)f((3=5s|zLIWRUNr{c`jr0Y)h>GZcmRd|&MPRX*v?B0jGz}`=?)P?w-s}bkfl6ZjY5M_~^zcwkpS`GG7C{=(BMud>n z!`i39hIJFEQ30%06_%^Uk?h2V4XN3)2qASw3iVO4`^d1(J-A%%c1Y1BVuMW| zSj{k{T9UNr7CgjTPTV5gY3i*{3tsk77T5_l9-ZUzj!?0$=fXc5M5IPHPEhvTf3yphi$%7O$U8D<AJ6!kN)iBi>&`akNzRogkpXOTs!5ptF$Ql}X36H>i zqo{e}lW^H$mr!~monrYD%39f`sHz#*Ddwepn7OQ+0GHHzBSO#;0<2#2-M)NWR5Vb$ zE=eZ057AQj!0zGm_~9K9FIgV%fFlb0WV6h{EaiEiUvmp!ytj^znXSC3w_DIQHCzet z6O)#$vTTI&INOF(vd8LOd$P?iTf%u&etBXyyPRw?m9PCBlw~5Nr}m2YxP70`vrXVF z>=cnqX}M$c_UQG7t6f!sf^F^{v6d}Ub+f)*k|)@>aqFG5oBJo7+Vn$qv+wwwI*mhljZ0Z`&!RWasG8!Mi@O~L^ z_KW?6#nzqmkJQUdUR~snegXOG{A}fRT+=s|9_U~ zA2YrGQL>Aam$se%jhxYl!zH7Y)@J@fIWvcfty+eFSWFlg7>JDAM?j8fSHe0Zjr{@8 z6tDx=8w4b#B!>M0@IgMX-l!~*E{I}$e0ep|;l8!)>+=O@hb9BWvca6IFG;-)>H@uC zP=Z6u4U2y9714dHGY}ujMqNjrdcl+erkEv*S6Cg|;{pBlNe>YY290w}-MNgzU`FEJ zibvY3hZ`Mm!GR|_$M@*Yyr)k7w3w?R)t{kXd|h1YI&x1q!aESlw*QsREzQk02}t*i z?gg2zT(mrPC66d>WC{3H+kx;JJ%v3n)T)KX$SVXw)cRol;?CK&e!%FkcrbN`7T=bAkULv?V-S=~*&)RpOm)n(&1ESgZmp$_uhN}?xUG(l3b90Wi)26*IH~=N1bxZG zY@VA{(?UkWNpksO$zl()eEJp4tTGWoI1qf%^@{e7N-%}w5$88X?=b7ozydH(0R)WrV2ac!xME0dkKRS~ z%L1FOSB=h`5E5TcG~<;cJK`PpjM>5O?pTK z$VvDs2H>Z%cd!s>%|w-I%dVQ>E*`f{=SigUM~9XB2^Cwt^#1xP_4+m!`w+j!Q(Y7A zE6%KaK0P%?O0p%YaVMH;>!nY>{YKZZL#qIO-vzEF3`CUSddvK)SRVG8D6KJKs?MZC z)(StOEMwOtu5+s7VULI@arZmXUa)< z)6fOaeExBt3-GeP8>RRIL-2wO!QgoefeigM?kF74@B#JESgK~GV&9j}##rQYWC(e( z!G_8;iV4W&F=*<9zXUV#OlL~s8C$)M5UHELFg^sPNJfTADMWW)*EVqTNF& z^N5vGBT!4j$H|3bMR3fiDT*R403P6Gw&G3W2*~Tr>&DgOGnM<`$!{As$Zi}1ADU+x zT@z-DOn!dFsr|80dW(@s0VepLVO&Ot&Z3<31zHk{6SJd9z8zKK4;be^x;Nr+2#Jrx zONDB}KTa=I6nSI^gPASBM~Xe=imC@ldQs<)DM+U;Vc2YkC?HofLGM}+V#W`S6UU8x z-!ciQb1`Xhg*ATC>ax2}4Z$FD@I$CgD9fxQgLJnjZ~BZm=E>kbfiAb}Q~@o;k1^mq0tBKPX-Lh zpFU=|GNPk9UmCe#OAH_La-l&PpB6c+!&XPK884oLXII?GikaxX*kLo6IK22G1x(Qg zvzR`aU0@Ga&T(fxWB$y&4QI8)W<;Sg#Qh(>Bsmn9wQ(Hu+GWmgKyTKjRi3%lm|OyOv{KOO zc`eR^@KBtz%W$GjTESE5M{-8TiEsQym^mS}mm&)9Xc55*gpQO51inotFTRQ{Ez{h1 zsdE~)2+~~3&8U$ClktvsE38el=qRk3qf<@W?m;70DaC|J*<%V^Vo`BwZoHCVtX-fv zPms!wlkw5)MyBJDoM=+4PgWW%^efkBNv)ys`-Z$%Qp*kVK`hjib{0KFbYZFgU1;18 zuf3$1_t|ZIk!XEUSQ?3WRnXDdCM|7SpU<@M9|OGQhXkl-KXAMHzmYMS{$IHLWK160 zE;9-me?Vd0iYH4GInsvE^t7VT&{5(QX)IJ1mO{f+PH4tIvz>f_nOJP3h)8H(zkCyJ zw}t~_)Z*jpu3N9SJg406eZF5`p?Uz)%+XhLgJ9zW276_4WBU{Vj8_ohX#;>@LjZ@u z@gawX0gdau_q}g2X2Pp7>+P|HmPKhlE2929IoKUS8LjglWXrrI`*>Yx2Rp^t!nNtF z-0uB3Kaj(E_K)q+Y3DlOvhAE*iROC}JRUml!h#_z=KAYx`cQhE;o<##^Y@4daN;qV z_t9gkL{udJ6^s?9#@v_vR$y*MYS}|Dbc_mM)5*BhaaDrx61}K>A?Ne4o*~u!vQrit z>u7N9*DWO)ZGEs0aSSh()Lzv$<<4jW^QKWTM#Zi=C`E-)%@{!>ieF~CmWJLttRdjw z`qsc%j&QWajAQvoJ#Q|zW()^Sz05a@(Qj9hhg4E`pLrW4-`^{Z7|888lR0Z3-k_IQ zkH}VX)MJy*X>n~1r>V}CgPI6hAr;EmuW1{Qxrt#3I4B{8A&ehYqUsJ-(p;0-%i;p$rQeL3Jo;D* z(l(baujMEe6UfUKDe4j$*N`bLp_oexXeky8;&`^T$QRKh9)&_@iC&GH4$jFJ<6n(& z$?h0nYPHKPq-xjY(ya``Bm@l$WMVB(A{xFq-0Pf7Eq4dzLyCyuTM`14!+;VB@5O68ME9(u%iG>#Ov&BUZ7egcF68+iUfE!XE8=xoh{q zfU%(vrXxX?;p-{%B?R6>9+?bQ+Z)H<8`G{Qk2N|tst_D}cRofM)RlSB+o`r3#A zq%|)-V9AyzZXi`ZoT_vtQ7vNm*jTowX+D&F?Fswn62Vt}o9(O_+Md_?H?H2PjGm6D zB@h}EdRUjQnkA+|)`yIuomQ9ksF_zXuzr*YSd@-V+qh%3VS<5x@idH?9zmj{5CzRf z4@f}fR>k^fS$HGuicMR0`8Lv%&?N<+1@ro!IA6 z-ri)Ce)`ued40wK8k|4$$vz~r2a+vAcaGu$>a189J#!qVRAd37BvI+$(h2h_PUPDw zt;7`oH0WZ9{=h33wMmAlSjX>&FJ6Q02&^B28XJ@QrIy6W^?euCYom-sFJ_!g{^6GR zSA^Tt6~`3|(kH;s_p8x{)TTL?wbMxBr`V6Eh~O&raZdhAROjN9Zpmeik+CVcW%^+H zp>F$w*F)y1(=)af)3Enxx#pLelJXD{h0Zy#DbhUo=BHe!;6{WNU;DE&Jly#gHvnj<|S4s_)=u#Zr1Q>ilrvl&45s5nl8i>BzMrMkPe zKfl!qyuA=m$?Jmltw!vF^6kloBaYZnDsXp3f(u1s=4tS)js==PqKeD7+RK7!` zX98Ij+{pT$*hgDWR!$v#|J=h+8@f)6Ac_K7L9zl`L9hZ@K{YX7y(hiP-Xnm_ec@)4 z@ZPV{>8;?bi(0r=%*RSMWx|kivV>R%&6i}Q?oz)jfSVlIv~QBe8g+~%WgSy!nf7n$ z6|$Y40@5iXP+28Qd;OWV2h-i7?HURJ+t@OHug|B8oHLVnC!&cHAk5miZdja+m8h+M z>89cSQ!R!M0vjqjb!KeHI{ysaX^3%*M8kq4_Sq*lt^4Kk`v$^W6t9L3@2-1(82RS! z=2r}xQK7dTL!Ou0a>yEDh(IXp^-xgL0B}S%ykglqTvK-+*PB-HH^4uVoU);ie&kPr zqXhro?=1hPB_962zOy*F+Zq3lJn)D!yO-WTG$RTHW30w*Ncat$YonnN}VPAlabHA?;$~R$uGNkD* zR@=xaM=M9`bHV59`MT>bwcuiPa6JmBAA`=GeQ0h1`j#<}WlP|>!!cUF9MWfWzgbW; ztNL|whDJe62Fk(U2(u1J6t<0UKi(Y(zaSA}u3cVfX2okRdpEKCe4#Ms z>eyR|^>zL_0sLhXkMyi1gS4I7TZck>FHQ{TE61KK|7G@i4Sz5v3Zo(hQo1Q;89X!_;$HVW4~lGDCgVP1DFo zv8s2{k-1$(!@RHDvM~>tJ@`6|4~MY`^bv$69l|J=JH%xa?PE?!!nY{*NYn;p$zCl8 z+_oZm0$kDl?xTE_R~(h-uLLyJqg8gNOSPAdn!rh8kCM-F?8mjCK!_>yyq%&p7K6g; zAG{3}@&q8!QeTC4zTtJWnJ_l~G!NTNdSxN{zd%FuVND<=bc<904LjGj8ene|128_W zLG*a%e@8G>VHgn*@?)3yfZSnp7wkY*1*l;A5om>I0~is2!NVW8D~VWLlHW0qe;f)Q z?90_TBjJ+q$hc=bxh?LqL@)Df-MG!;b8ZVbP4YfZ9+m6jKLRoKf`QNB$%{R+bj~e1 zZLz>KU;ZlHVDj&B?f8y(?hZGmIOn_mR6LmH6Yn@@l6Ifh0ese5;@o;>KfVP>p!uPm z-x6%;-5^k}qzj(cykPw!a3HB>_CkM72V@}sd*J+^o^d~BlmANg>7Rk~4}dEen;1J7 z+Zg^Q(leEQtR8UlZ0cE<&+>_%*G87Q{-%*CLtBeQ;7!y)8{4<9OBIKG zAtCwt<1x!eq`kbHMJrreshYWa^;o%FPi1l)XYe?h`hI@h!Sq015zqM9Kcc0bKO90HaO0;$8)t0O!wRogN8;?@YKm4qmQNrw*&VpuB zrKF)obQ6_V*afOT>yIJBv|%(erj_i%8g)8yqELPMJ-D>Y-OQ1OFFnR8}Gzw?J-ol9PvAr=crR+>$+%>|%oa&Yo}3BhPeq*jW7=F;iU#1YLY` z_0z+=rVv20rZx63djQoBB$qMa^D}luSQBoc|3ZpgK6wc{*}rO%o#`?i_Qij~uzx$A z@DkeVgqxR~CSE&9&6@>xiave|)lSn2n1DU-5Bi@>oTv44lB7@1U^h1YP0o7y_$BSx8My6;{#-y;$ zy;%oRbCezv1W5A0Dr^qE`)obf(uzA8`gn%YmK<O!&|GA!k zv&l~sD;OKP8(JAV{KuY^skE*5qv80)x_S}p1OzNW15CZ73$H0a)sU!5qYfAtTo9MQ zI>=ztBnc5bFd8jUzWCUQGA)d|8ju0V9$MQqMpQTxR~sC-FlpH z&2`+~{v6%y;|;mPZ38+Jl=!R7UU_g3)*+7a$W8lDEKE9Wl7;oUPTeqmH-S3+oN`Hd zBW4C`+<5=B@#RB+H+E{cijH<@nOF=0g1=LtYBAEmdy|}nnhrs*?~hn_Z4y3~?CEKc zki}@#9<+F$xN2~g%;~+l>V_kH^rYz?8Y#IxK~?*n4g`U>yAMsav~FZ^hKt9*Dgraa z=H$T3WPd)deL|Y9@tGyZaR-Od{5aWuY2k%~h`(t-(FF=gB>|0Yi)yn$R4z zpX0H2xtQ3#czqqxCbNZed0V?;m<`rZNTURYg-V%9gFIbQp7b2cc!z0H#aRRJwiscr zmql)lD&`^Q768v%s64Dd7DNWbe43bZsz>J9^iw&sZ8@|`_1j9{{?ZLrpc1e@z zm@@myFx9)xF#B$r{oDqasCF{_%qA=+7qkW<{S?raZXo@LLtpEQv*;5TG#H0mvrhW! zjOT&#!oG|-xp)K*GrCLT-H=i7+sAjZG2e0HeBwUc<9PUV8wz|Y3Vc7ar+2Y$0JsrP zK-^fB!|L$0aik5P6vJRnc}e(xUP;flXObi?J3f#&hQMo1Fu#i0O98KFT`3wTpU=4r416mDstrqU25>J(-{Y%z*kC9c&<%oVl;z_=!TY@x=xbwl z0^lY1CtepXbXs=w^UfChVO$mr7Y_}6LvT3hgBy^5!uZX&8wVm@|B=FpHP zIoXB65{!bD0XK21qv+0C;S^EfRk7eZDvb9#mtwj}es#IeP-iHa<>xV_uKD%ey<;W8uqyY09`@4qG$jl&4u#RkTH4 z=~LuZxCARvF5biP&E4=m$7HLaCkK%UTByz`Ib-x3>L}L4U!ea=o>)5-?vX!mKN8}f zPz%d{lI(umD)}88^xXxV&8>|7ce+bf)pSxaMgEdG%Fy?VI|3vkC+8O<;qxO<0G}n2 z)-w{&_?_p!bTBa??x4qjXgc*P-_^RhX-iHMs;Rhf(aoGPH6ai0k-yx<-F-$m_fb5R zhSu^?STl0_>-fMZ4cg_F?)~DM_kH_)effK09rxn}(hnvLHV+{s&gz#m+eLr;uMIY> zvFt(t575sN1WHo86>ywzkdJTbn$2I=8Rs~V@V#LrY%tj*Lkl*%6YL@_}=C3FYGEWn|M8crJ(q*F~%UDbomteF!_7EAgT zR~l6VI~b8m&w>|BFUDeoqn&zir0q(%5+Y8+yFAvCnz~M#MWhQhZ+5R_)I>Zvs%4#w zCWW-vy*&3PLB(&9g~C@i(NKK)>e8D9mrQX8v!Yq66;9K)P33DuaaDE72|RQHNv(~b zB?D=#iJ-X!A9cLFHpGV1IY^?9Q9eZ#_Amo?B5Jma@+Y=%q%lM84f;inmL=Hv;o&lh z=(Hh5V@We4rNi3<1ch}J`ke$@6n<0pBsV++NtCAcHb4;EmxP2u%!Z;eDN#0Fj=s3` zpQY42pc5L3Bq$*FOWBz;CaNE=t#${;W5}Wj{rKONJOssu&kDm4Qud;dm_)>rh*rtN zM2bLH%85Fc*x8D-d&E3UV5XpBPCF7=Ym9CA!;1T(80U?KDhp^CzR9L#IUg zC@`gniKB4!hg+E_RcOe<>&Z%J^rbSz99Z;jV@R$bW1mPG&(jyKCn;1@QS~+bKE~6S zB{%AbvyM`!Pl4PkTtOPRTp1Yy-$M!sSP3}7yXwurYld`3C=GOjSPxhsKQQk6EhN#3 z;{<~6d)IcKQ%V^=k>E|+YdZ8A>=vkVOhiQl(Ziw0*p2xLf*4}}&R*n$7gGrLEf6i_w7(5+YCjqe7P8HcT&5}W@-E#jqdaA+iaDq0#4kVA zT+P$bdhC?3gl_JQ=ytOaYS8VUQKb-%at6424DG$~BA1yWqwQf{3L<@;mkjuM?Gec;R(xVOqub zw;OQje&lxG=Uyf*)DyjX5DHr#sN$y7&%_&6&yt-UKZW`Og_MdNd0bbUs=opvO#yhS z^c4neJf-vX8)hANS$brBuy>Vm4lv_8cla0mq~Vbh%x8`)5=e@Jft=Y$&}-ynFl{t= zct$!WFm9#rT%*N%N2mcCQXXKuXU)HjC@soNS>&5oMzGJKRQe~w46THkU$hW1{sK}w zYYW5R7OTqRQ^wvXs-ewA2$f{!Sea&D_Ev)z`qNBcXxn$qg4#T>wMpN!64m&x4Ytft zmZnFTCqdw1>>T+#!*ABGZ}D3G(c;mpk9_l#59z4K>bSSa>l+mUGA;tO8aFob#2%ks zOX(PF@DyA1fv|+UOnHsPzRra*NdQ0GL_!I+5HsncHdY7pto!8&p@(}<&u8~;{=CH% zw+TC9vvKI>RA}@E_|gXKFq360E$?9>?;vv-2sY zb%a}9D77xgy1OxpX6a5<)rT^jLq8=xUEq_+#&iCZA{XiVj8TTH zy*|t->oduOje)AG-{~D(^b&aZl-_0KGs)c#GloF(`$SVPBOiL|)It_5q(Pb^i)*X) zns#v0>Y|r1B+MT+2Y^?I^_wvmn^Ad{ppu33=62rMFsYod33+^pBKrBnWtyp&pr$m@H^%mR$E8bU zpM=iA^GnpI5n6Vfts~+)N*&K06XUOtt{ctZ_Gq_*^%uP2jk7T>A(@)Uuzm4?C!yA$ zcBH9CuVCR)l{dFtY%iUz$j_uj0YumVCdCpkFsm0|gg}^pl_UnT~ zIY)=IkDEYWGKV^$ucZ|**_CZ33IKrQhJ2_9h}KwYhtF36o;eN0?mJ|+an zzJ;GJjX;j+4t~0dHRE6RWW1G~>5mTN<{IuPum7IJy*yZEivIz8zkcLR|94#q^M3&P zgp5t}ovob2ZH$aHVBtgfWH!tR?d%9 zzXwlaWS7=Aiuc&|iRgVQsM_Im8n_gp-87Cn<2y6_6C>U7ng~2GE$uZH_>R#L3tW*tv)qY*2{A>WmHRb5as2Ef|SwME$>(|+Q?(6y`S;vqnjiP>z+COMan*x!Wy8gAudf}ff_ zc?4->5;RJHpQrAxsE)^!o3C4M5P?yt>0 z9{(EMBz!uV7TK(n-YTrpYlO{KOC$1;FnA-HSX=t~76D01MWA9fYnPfjo*<&xXPTor z@rXxtn2!VaR&tn%6l+YL3fv&g_qJW2h3~13S7U&wj8m<$YG(xqBHSw6#>zdOnruB^ zx(66I`W?_Nb)Mi#(T z4Eom+>VZ1`Z5>9|7GcX)KaGS|06W8UT!uFcN1}f7Y3S2N`H{Q0ljofkHqju&U_eTRFeeo!d(k=(sQ7FFS&DsDAyU3k>mFR{ z=Bac+F3@@^*z#{&onH(4#+pD+1)Wc)8(#8v4qA%JdxtBT&Cg#U3Aq~!i|@}0yFNg< zEo7m<{!m|3QPF0XZY4+K(0oz9;Ws*T_T7@$AJ2qqT0Od7ZJO9$$(?lolx{)VaQE3~ z^}qU+T7>i5DV8_mZLFg1_OLE^!ecq6%F6z8P zU1U@eqj|>+Jn>P&_^(y^YpkL)alW`KpLWMQ;m*=Of@{o;u1R(z@O-ujhXc|Azm888GlN(RlanmQ;cfm^#A_1=57xi`(CHin& zfZNEq(It^Td6TEw%vbS$=er8i&to+xf2@pFLr-htjZXzHHb$oKj*YXOvPFQrEE}gP z;h;#e?Eh+x$_=~hy*U%=25%Osn&9E2Izf$iReClkW|YfUM*5AC`MviN_D6X%At+{# zcv>s#oSU>UW?P61)Ik*#_zwK3Kb{us=xpGxw4vEvFo30)GxvU=G4DvwTIMX>(_sVy zB+b`k`C!fpGAxB1)rP~&KxZ(D{v&VORd0ZFt1m%olNnu3E~^Q zdV;!wgvJGySmxk*kRUO{>3@5p@ZOeHo<>G>q_QNzD|B7-Syf zi<*JM$c};r?j&86#T)~y5>klC(|RX8<77f5*g?vq7%e@|N8TkBXivphvzSOxM{M^H z>XI0h+%x;!4K1g4o7=3*{&l}nfkxi2ScPBh@a*8AnJ5FuDLLY%F5S%yj`eXI9OK6a z#jat^62mfG$#uA|gz@$$$AUs$1jo zcMU)>KpDyM(uCBdY07sSFhb^-(8RU$#nfYH+YBK?rX;Le+lQogr#U45#{V^(F~t^j zOJsTW-eugVOtaZ6v(IH&K5wocn?&tCM=LppD8#2;Py5ZhRd<+0TI9uetPaU&2hKGV zm;T!_hpr7t68cpXMpVO@-u88=fSLqrI9W@hMlhpojKX&iCX;h~s2dKU(SE%C06WAj zpKUrW%_)^E@+sml3@rMs;0DLhfZj=2D{=jPP-I3Zmn8FKTGdH{XMd)_Ui~cl&u*$+Yrzm}w-&l;5MG z!K_72qxH42!^#h5Y$5vC%XA3?&e*JWjN$Sj>4l?Ov-cb_yNz#WOZkP_eJ|r&rx8l1 z71NZA(18ffkyo9~R0oZgj83Q#@}Jjits*pg#)0c#w)6di%KBsVdx#*j>jEi|L2MpoED|a4lS%C>qyx`fvl7eI3tgfa74 z+uY%ZLL$KG_V@VDH0pOE5v_;dXQ)K0%YGT_+^7nR%i<5eDI;Vzi{)=~Lq4?H8iNN= zG`0$g8*j1K0LzJ4%}8;|n&Ls;{Z*O+soqzM($*Cxf+I`;X;UuK@bdrU&scL*#aI^K zp(tQ_nwRDEZZQKiap#3}|4J(eaSF})+JT;_FxU`zV`RwWmwB&y7TfW{o579u)|BIH z=ODw6eE4@3(v^`P$@$4b{r}iu7x|yYg^aD;e~*Y_1x?EZKBO;_Olg-tvrEl7XVt3C znneJ&)sF#>0!HRg$pPM((xuKztK#A&mdL>TH_I;yUSwo1Kp*mZ=~fph1O&|bQ|yi= z-Pc-2A1|+uoPL~XP;!GovE{#DNzRB$yeD4L9_-7UdI62+q*U3x*9cpOe(bgXnFS8O+U1V&!QV z+$0qw9dq;NvexzNQs=27I2RH$svg>{&LnGVGfA7|oxH=b3AXh#5Xe5=9=w z+v3Yj)Xcu9+m4O65tX1=&70BIdu2>R^}n|Hp_PfBf(-QN1#gUa5jt3$uJ%)`^>HkP znUS2W6Aa1ajr03F*2aI|Dy=~deJ>OQ!*1{63uN8S8o9mnSi{N-2Cf;I6AnJgG_GBR z4bW|HB0uU8SO7Uz>7|-C(wsymc`f@E%x+(~J(h_=yUHS>Dl(NRYv6dvom(k0P0_3_ zP$Za|qsE&QRo;i{ff=!(5uB=%ddBKE^nZb5QSLBlpcQMPeE6vhzR3XcqV$e_=|eOw zp-rJ0L8IEm!>5jU4Vh~XB{P(+8_UGJzXD&E8bYuW+rp^ER;1(CeqwCzbaDwDLTv;R zZ20W~X}78L))L}z3#15^FP%P%ZQq>r{Cf^aB9|T-@aNXO{G%k9<9~ST{;MfmtZwOw zxQx8HoWh(!rTG^P6H^dN41-X9pfX4g??qfVN}6gPNo+6bupXUIQUt>gM>;W1be%~$ z*IaUKrcqiTgNYoT4r=Z=)v3B>5qF06(&D|#H&5IAxeFt9sNn*wPh%px&GCBmJ?qbO zI_1lKX^StI9%juu{DA65Cs*OFI`7zR7Vc#0PCqCY?RM8rf25oEmbbD_Hwmq7YHI3X ziS5W>t=UV7m)m0K`z^Kmv$gwc4DR;e^TFY}vfHoP(Q!D6TS4}F!nPd)Vx8q1Lm1xK zn~)Gm3?GWR0#_?dF9}(+RW*wRF?A6UJb20CC@E8gX_M%x5LOTi&k4q%G7Z)iss3r; zinlq%*YI2@rK9Sg29cqKL_rY}%MwLV18u;)JU(Yrj^w6?B<3R0(rCzoFL3{(nyZ{A z!mhx6pwG|1e!G+aldc}QfK<0iAlEU0cMGrS}%=wh`C>M0YH>Wv{EPAT*3;ng-vG*hB86!J3 zZU%j#s?N!bT`3Oh!Dw>sLQQiSp;W+h!}3@nm{8MCyQL|<9)_*#Jxs!JH*7(@_LMMlxwe!>&`{z*@wZ>8GM$4*j`{ zsf53L1(h0}q!05ROy#9Rg)H8q1?_miO;D-!@*4zG_}&unvo}yEe!b00oY1KQo@S%c zk)C-3ECr_M5s1VYWd4^C=<^)(bi4xlt`{!l?3`*;rp4%n+xqI65^t9c4GpogjQ%lm z2I;kw=*0RJpbZ43JgVT$pURO*p{0*$YwQF&*9;{LA_6T@^8Q2qr%A>#7=k8CM!@Te z<&DLfRstJ}*AXHW`#%$CNAid2N+tdVip4ec=x0y-DUGF+6ww}lIsV_>Kq2p_OAkFsuZCd6S+&`v8ao- zPc-mcvz@yt<*(Zn3mwriuX~r%oSG?;PjMYbfbXl4Q_9b=%pZ%=>=!MXD0mMElz424 zRw|FpRz*I38`-#GE2q#FVA&7VImi;2`(}FCJ8~6k&ZCz+KK*TybZ`BzsT4{{j5R(> zxk&buKs@Yj#u8m77tly-4=XKYgeV+N(w!bM$YdSQ2Cx!g@(Z2m2i{tx7rt7frR@N6 z14{*338=={g4s!RqwNLSU;5SUmkq!brVU^<4BN(p()<3B+#93N3A2NT6R(CVrU(Y; z2H7402lT@1ad0T{OZ8{BLu@as(pE!o}~HgLCS#d!|~ zO}p-<%teu$K2C}IrR25d;z%PMUDG_rTmf&99D5Gq)LJHT^=4H;?2p(plEPGyuZ1s#w$T0t3__t@zf&G>kDe6>k%F%g}W~S7tC0hZK>Ab z%Pc&XMKFE!3|=sMBx5xcm7Z>oj=(X9xAqr@+ITIn?>BWH`lRof)`n4N4wn^n2eLB* z#!B8%cZKnpH!Gmy+MuLj7ex$ex$8!lNv@YCd80C0wL@4N=7?#xc*mMwoT>gg3X zs9I{_wuM02JZ<^*<=la~>yAM`cJDCDAVqEP;-1a0F9GI_I)Ie3%8kdZdVdi*WXR{( z$7@{2X~-&YhLP99t*l@>iPhYN62_l{ZwNW-FHH4?-qU#c4CmnL9%xVLOf)~!EGJ2J zNWME@XL$9^l5X2QjHhB%9=uE6du>X=`I|<(yir~CJ1$Iubi3!+>J)|2lGjkPqrfi1 zW>$74%Ew9UiVm4ruc>dtn?MuT7P8zy2q3)DMP@`g9gGcw&tgREQj=cp-J{Q*Dp?~Hi1%q4FWft2pYCuWY3u(C*UI2Y`R z=CB#3CX)AFuG?eaLP}LByNaY*TGr0P2V)BAr;-GFKmdWnypT6VnwT~9dxPZP#yctQ z0E#w@BOyMq!Cb)95p+HTNrpPnq(Q3%+8;z<`R$MY_>k81CRrhkHAugn4J8z}s!NL~ zYU@#sOnnzqn3P$@-6#&~tjH7Hmf#)B?webE48-w(U+e{9>6T{R$DC@lNu25E0rQM= zthJhzK;fO`?Iq6BP2lAP^|DdWw0>}pdG|{F`jMmNixM@&8ja%Zg|MGJ&ym8CB=5oGEN@T%|0XW_3d)|%)S--cZb4!Crpvz zf;TsB>`Ku2RbkXQ=R0oLSDhx8MuEnVii3Wbe#C?jBB_2^+U~6gh}FawU`a&a*mO05 zEK9QtmFn~CM*higTJd7U{X%01<1bKc8DI5Hfx1%r%`(S}JBOP2M~*ooOnuV zg{B#qXrHOrLSBf>L3{wL!i-1;9Np=!6BjPbA{?5Ap5>Yx;eF$PV3+w_fn_z6J)EF{ zF6h6YsCt1wD?1L+b8yN?e5F_?qSo6g!UCBWD=7DJhl*B)QZ0ot7ivYbsP_!fhC-q#`N!Mkrc9;TJH7~ zwl}azrquCY1$1o>v2>phCB9Da53-zcI58yOIW!qw*0Tqe!inEP@dugEcfgZG=^x@G zypspH26v2+M80d{UsCl4lz;PGSVkFtNO*-IA!@MctkMczU=66cexcG{mE%;8?k>>c zU87P*$Z&BiUQGo}1}lNDNtR;B^!~VoSB5U_E6DwfKOWrZ-S?jWF21qv0?0i_EZm)y zv&YYW^Nr7=Z?c4+HVGu=Kk@MYLR z4N6r?oUDonq6%&~juD|IO=wxnYl<=veuUku7Zm4&lV({&U#OFbr?xJ)aMxCmV-igh zjmM$aEUESTN|i6^qyr6X5!k6RtM~GeS%>*lQE|b_1rF&bxk&mqoAU(l7H+W2;Lc2) zwjQd2Mo{1IN6u?#rSIsNtf(zJ zuZQH*?m|f!d`k>TP~g=X9esztjW__IKgxnADpw;_?o64K;#7_C9j&@0AS6a4PZ*9W zLi@=Wf;PQQomdNXA-sK`^XxwDKIJ&|{yF{Q*(wr`a`cDSN8)EqEdjazupAl#Qn*h) zfyfHSn2+Iap$+4u)-Z_Xw_b4sMXWPNDA`=tyXk2UX?ydH1oV z;ryu^?~Ez@^u-J6pJu0v^YLR!xmDSk$!aswq>Iy_BaGsxnv+qna&^(P1H6>x`oT=X zrkrj?M7f6W;p|&TO`8*n1bWKul>JU&y>dXw<#7^z0y7o&wRQFRR7AVY*+oNj`BB9g znw73Iq|GRqB6Pp(us)s#fpDKQbR`=m=k*>uQ72P>U-6j3S#xd- zFmbZ5R3ZxbWYrujYE(gSdyb&fU9wp>V1~7M&i+YCWs&7kg9eR!>%wc*F}LG@5XDMP z;i)7jW%FrJnIJUHK8DGNuZr@~b=+)BC4)&kM{pT%sy&<|!x4HF=Cd)?4nSz%_en(* zixz$#ttwsUk}r~qM9gQ!T#p>npCo5F5gUDl_po1zIq=zynjw5BGdQ+G zI#8rIc<{WyJubg?fdV>wqRt;|fj#v;<7Omh-)4n5+Z+&CKVGmNPfVE4Xa&I3A6|BN z1a$F^es9A|T?{;B*Bt5fa661Wh~1j}0P*doS>cU%*G= zKpRFG#OTlIy6389p#jJTAr!uame<$|$i(^n7EBw0o?PNmP%BV3$nyN)NU6+9Ukub7 z;E*a?Hv%c+nMt>c3JdEtbc#IjB{SGz6)5Y^&amqOHrD;bH&UqF<;HOJMn-krW4~)(r>hOnEBL8D8xz;7f0){mB7nz)D`u|7TmH{^gofFg0iand{ZBlsi4 zBZMQQWO!smWCU{H5fBpK5)fnHB=AXyQbcJ&a{P4ZdXRle0p`H-5GL>wL}|iyNPSiT z5&?w}G5BeMc2s@#z>W|{2ucVOq-7*!_+`Xue06boF?tAn1_2%c`w%JcRK#X@Z2o%a zec`~Q2vnqO>3SeXNgm#594?TW;?;`fhRQgW6Q)}NW`@!a9qD<&ddz(o0V)B_TrL_o zmZovIGXfg97Us|u>z{9Y+O-xM8TF2MN-fflwyckK*}ANa?JQQ+OMf%+HQMX5`UcXn zRb!!@%~X1D=+NsJ(GPO4puwsY(WuHWcy~Wwpt*Fo{TL+H1NEyJIc5scH!Wnrdy-nt zeV7~hnFAiyUr*}a0#9y(lz0kM=$Mw|6ALj2^#DMj7~|YX%&9KtscaYM8ta<)VT9#} ziz;3(*BAJkXo^DK&)Pkj&3&L&C8h24gq82u3XB4g@}Yzg-Yg=fWvJgq^?dHc$&Th@ z?=#lso`#1Lve8@4Wzw^R{zN4ipN??bSb&0*jT~PugDOb~P;{VdqRC~~b2YV<7^W)h zpfNryv!2zGIG+KJck)lt1$w0QZ7jH8Uf7RcJ}w|P5y)3+GS^M&34BNzHnylTGHI2p zD&K3Olld@rZY{u)qP9{?&Uh3eG&Wx4hb>to?j1L*m~x&j(6c|4)1#?G=gTZY!dm3x zgAT6DGQ&4&1Zaf0c-Eklbfpb*&^@6@Hx+==C>1kDC0@dmmz3}3{Tc2VjB#dT(3Gu8 z<@Y|~k#oQ^TWc!9Gh?}zqkAh3Elj4D? zjckwB)rU$1>YYo~ddlrs-wVdV0$Lu6iMVhjkow}c_=&4NV)a}NExB4m>yuu_&9dgA zED+X`s3QVR#(FiHF)eG53hmPKr`7#=0t!>3I`(3n+z?SKvewAvfa$;FC*yK8<-2R; z<1-*Ceb`DiUMUu8mC%CNdxH??uKM>fJt*~cN*V4|h_1j_cSaw!p zBI*|gA)a(BMi*c@y&Q`Gw@NXCQapbZoK9w=KsHQ%K* zuz1$zopp7QU<0n=!^p-v*fhv&)^~Rpt55uv#xNv_HVYtpuliDhMHD?6p0M|A6EaUU z!d$p6zq_jhr(rpYHb$Y&u8geShM~sCliJPz667vSV9P09OXII?$dzr*cH)aw+%7mH5TN>K zLtm}Z$DszK4k;vJr^5TNZfQ}1jmWP{Y>T4xZj!3#(-Rh^2gPIu33DmZkdga1TRrOY zgW|7Nd-T~sP+XEJm`-=O{d1bj*=`kPDN~d`&HCh-tTI4ephT_x#>kKBsrd~r)>D9B`$@KV@{P|b%du1>ffIE<0eq7CZ-R-&o zQQihbm! zLShxg1TNUeb#sHpB65}Y;=4uczy_Ft=McNa?0^KILd-lp-++PW5TMh}Ppgti*Z%Gl zx#8G};L+AUPm|T(d^m)0OYFOtMz0dy3ik5XXKy8FqRe(kzJGgrE ziH;4k8SrL_U2L2!{VdMr^s2XK{*=N~ZdlP=Wg;IUOJ)wOe4T{c(=AS-h#fwfk8S+(WxYN{U$T%4`_{<$iLG7Aj^{E{+SP$?|WYIj2KCd+Dd6dX@F zq$vcXDId{`&ycfK{EO8Pvq*!5-=ZN()W#2KmHZJHSBx8-+cbHG~*n*i?v(>(=oyLO1P%Vck=bBVFrbeqsIQBT`=SxS1|276f}t4AL7 zM~ifFKFTNqlfEjGNMHN<;1qOqU=oS+YQ08(cXEEyCnYTb5WERMhc03C!KuLr$pAj!AX!h*NeSfwUR#SMTl3Un)9+1h;yeAd=E#~ z@Ng7-@P-)Ax-=o@2D87kq;*teEAOI*5EuN3ZTmy~)Z6;Uzzxu6>^*YVwDq#V?fjRk z2(`mG!UoSNhCa1Ot5RpR7ezSTQ6qTv*GBjasyG5pKq(Pr6@OUFp=9owEL7xf+`|hm zKM{38Ld5Vlz8i^5#-!q~xG7veLwuu<#M?O0oGG#Au28&FK+=P%c+qqlc{VFFX?Rg$ zQPDJMeD_X?(-a*Qs(7iGxNo-5TLIBWqVU|Q!rV4d{MgwzWV}<#kMGj$TSBCF%Rc$g zNp3}guycf%CVBoRVHUobx44V1Cb`3uxTCM9I>uu;fpWFbR&GMY8Rg5K3#m47!q^wW zlj&D%ozR?WaZxnEX@h-YHwp1$MA7)L^qWW41IF=p?xWaQ8xB%vhq>(};!8VL$PqUk z%y%PK3<$=zlHvAQSCHk1C(E-=v@E{fWZbYuUQ%@k!xCuXiaA}Nb1!`rqJRRV-!I@Hja62W9MlqKev)xLSd4e-7)8G{0@XT30oMhiUu_Y2FLJ*}9U?8i1Lv zo07ZMz19R&U3A$i^g<^Dvn~ImY5q}l*(=t1-imMzc+b}eqW$NjL;Gqu~g zyk|3`v7&Voam-o_cPpj^EZ=Zh&Tk4+EBLWh*tEYcBrn+*&z#8D1m6xH9(a{r7pqS) z9JSdo%QCa)Z6}Xh4<29oZr-R{K+D6PsW)z>sjWO)+o_A;O?zkET+-Pud_zv{BTm8? zC2+>y&6=cI2rErAQtCIhF}Qt$eBVTRH|{4Pkuup&zW?GysU`40wa~wQ`BDB;&4ukh z=ug&ZbUpw#f*UmB{83tVg>{!mWQSgj+SWC@7Zs- z(}kXuE}CCGR9snItyx{KqN%C5E+WkoXQpArQfav+R)x+_(&mWu)K7xP*KnU#Mk!3;3%{5&goxA+ZpfGwg|=uQXlHsK3T9~`)k z*bQO84^;}?r=@>{PQAzO&+=7A1)%Kj)o5dYDyU<07i`*Wa%T3j1 zt(Rzv)Gv+Nu3G)&i+H|?=IL9(7)r`g^Cb;}ft_jDz4{Uw(&H;B;;tA|uw9J7x?>ub ztvP2i*oj-O!(6rNPRwe&i@(sPrJ^y5{W}66n}A`Hm^#&l?4ifcXB5#7vQ*_)*9xD{ z7s)pzR9c*$(CpzjQ(Fy&Uy&2vJKk=T4ttO;w)v)q-5v6Oade^idrh>e}rC@ zH~;GtXcd-bQ+N$9WjPsTd^MMc1~j})JGZF=<3LG~dHaXbtER#%r(4zCd|O1Y0Q|0H zxFfnz2Az&rIPtQ`c?(^fz-Gx|GO0$IuIOOYwQW;s$k2YXeSrBZ7oR=`nVB6}^xYLDmf54vMm}BS=eV%?pZRqiWY1%Xk{va1wcC}`=CqHm@$iH>= z7(Z4skGlVXbrY>gR%?^IRK>}pogy3RnGdxKCgJCHvG~EBqLCT8; z%er?Yl*EvZWYq#k#Jc@=EkBm79)Il;NLa4WDIE9$y3_%xU~UD?q)tD0t726EN3SUb({ zWutqeKR|y)Q0DWXm2|DUbY3j~{8;+JGR@a`4he5fa^zuLKqJjk3i0)9Bv#+pj!0l4 zK7`Ms6BjHrN3Bv*dgHG51HD_DsD_afy@#tgxq{vDr!?%-`@l|Xu$dM?``@|Gw2&~U z(?q z#Bs(FV#_dRqlNy80aN0`YuIYEGma$JW;Qe|oY&Orclnw$+*mbWDiW==>3jR$06f>9 zVj5!X3dl&Iaj^vU0l`Ukv{UcPCqIu1P)`ciWb>0l9bgB*zd>7;Jc!LkR+#ibW1#F{dN7%F_ zWXHI=C18RZtciY6?9-yyKfDVInu}M#32_x5%kYB4yaAJIa)aI)+Go1!2z?bi&G-W7 z72n5m=8C&9y#wGD*C%Kf*9Y&78K4guTE0uiHL(M(j^$5FkA~lk;Fi=UkMT@~WoVbR z!2v*u&IKz=$0nVTOXMw!zy&i+=aw>&L+F;n(eq1f+rq@>WqIeq+&Bc(($pzpVA7N+ zI&*y}hPYLJs&SwXvUAF&@0>aXQ3AuuqW2iZ4ak79o0%W>3qYt6)?cN`1Ka_2M-x^n zNDaCzEOMp+i=Ymzd3QY^kF~MS<=6LV^hktqL_@se(#!R)U}Z3TQ#6O?So)*)?bJ6Z zs+0b7CJ7@O-C<_o5uZo817l+0&yYVeR)=it@$~EnCw`KjyY}LC?NGq&>R`a!mPkq+ zdM&cSP?A^b-6yLW<)*D9ZQfCms81CkXK1t)hfZ4$dFaA-0KPm=1I)!>eNQPG#Q|U6TR1?fR@RC4+{eop_ z!ovS7;~@tzDru9d z?FiZER(de%SioV$x*)EnMGS0LH1Gxay1N-qepxS6U{F~Vmhpd3>b>>0NK{>eB zq!D38XXtS|h#NMoA6cM|hRaboc#;(IRJ8CdwD6gNH=HAE>Jc~gEiv1sY#QEA-tZ}J zRnRK`q^|hTA$hwNzr2+z6^l45@QWfK=K*bb6}(m^9)y%1L&g`k@1Q8F{T=90yg<&C zF_xS~OtFes06g8|P?B7bP$`ghr9_`i&7h6_H;B}leV)C*bvc-B*Is+7QDVW#8yqp` z^-v0#U>*iv%FRBGQ8_}2HI#1mQgBL%c<6Kqo}}|{6#Ze8sm7;YZzF3E#5KQzwB;3Q z+7-C|TIpD6lR?%Mr2b#b^onW50#*xIHvIe0XmQ3Oh-da)UYF41s}J7YEjM*klNlOQr~<#ZlNRbuL^%bS zy39hh*pR`!Rk}x}$U}|%nuUMMzLQw&a4{lFRPZ3%FA>r`Q>ela(ml3M z6~sNZA2G{`ayqR{w%r7c2LAY zQL_KpWA@8P>?KEm-Wnwx2TI=qBWG6%lU*vk)u2EXw?;G%5JQKx}p<5R-wD{O(r_NeKCSgaZ;cz6UgF_Q7 z4a7Toa$Hj9Dvc00I{fJ@34H&6sEmZAP!1WWM(wf{{zaGy!1O=v!#ycoMyi6WX+0`q zI`!F=$~{@mAH9w{v3#yw6@1hW64J_$SES4>7p=h6tZ60Ly-k9y zMMk0dnl6!mtc=tFHp%PXfoZJ=Ad7N(lH-YDD%!)eiT-8aRb#sKwd;=`p@F^K@y`|b zfhs*K6PU@zFq72IdS!!q>?T)aL0+6Ts!9SB@_EZND45yY7eQOj&j3rwzO7ZEdk8ox}XamwSL?FC4W^e2{%2U zarWA>r_4W2t>l=hi|fQt{8AnzO>{4YH!?vM_XwzBZ9%EpC&Zq|VkO00>tr7+>l6Ld zU{`7tTPgE73!XY8_YH7MatzUf@}aidK#-N`Iqt#Opm{ggftf&43s(kJDHPSac^ssg z*;bG=uGWDxguotC^@LVKqq9T7#bgWB2RgRr9~K5;VL@M^27=uW^Z#VK(O`>hBX*71 z_B$@-=H=vAO)KKlrA&lhS%MrbX*8+aMlY{ov;AF)72af&dfbrsM^!7dC8ThnV#_qB zHnjUJ4QYLqm>~5A!l|3g_h;jBq0f%>7q8PjE$Ps!B0wV(IbaP3H7pdMWh-Yeo1q_a z2{NwNFMW+?1jeTKux-WC?WAirz?0+ANxC`^?GZg{?#gr0df0OS#CN8xqJ9Er#(@kl6@0>jX#iZaawNR-fsYFL&gWxP8qTJFjNJfuxnrw z!bMYR&OM7i<2kxV7cBEb|G(0{0xZhrdwUU)PU%vll$36emTqYfmZf1y zX%LW-l8};8q(hL96andOq)QYLr34A#d(ijy$|3^)_Z!{o+G{W7zGu#yGjnF1XU=g! zt&wPmdTNrpM#U*8{sM!o_wijTlw_z8X0nZ(O=tWRMFAeBZID1)zU~%dnT-ANG+e&A z%6wl9@jK0jNe17{r_$K`*|@;7UB$Fp#34iWIBy7dJ$$y;#dyP(Gdm!y;*vO^?FW?!#qE(% zhwGxvEQbb$+0bHUO3~K@z9-clUN)(fnz!}iPsgh>(fe=Cxa;#i0I39Z3;?!NbC&JU8Lr#BlAVs%#1 z;*xb+U$R_#`_ZuU$_M@r6ZZGEegtqq;W{X}?)!1GGc>W>{~E6NRf1G#I>XZ1tUP~4 z5?zM)%gvC=ah6b5KfVGpZIW%srLwl0(WQ*)gfUr!-)O!DY_a%5W9;eUL+f2jRT&D@ z>Er7q3beZNA=2^R41*{%M>srLHYyF4KSE~WhPoL2S98E)ehXdaqw!E7P8vcW>;oSYTK)|NvRVxt^5rjKNaccVOJgBDpC@XhauPI2ngC4C63 zd~Gq1Tv?VrY#9J#6(0H)fcxBFV3?HY?LIN`HJM2Eh#l1|)d(~d-WfY3l=$#U_wbqT zY|Xg*mOrFFU3JMFsOn}c2vJ*U`GO(3vm&UP@iO&&KvQx`jPnf*O4Z5C9*tfkmgM5@Lh^u}*Sd-L>0l!!}Rm7P$WH)q&zDWnu zt-HMwQ%l~K536C#0e7^GEe!mbEySgM@OCz3`{lMAfk6uMEb2C=@OCW5TIYAxyd19$ z+p6XK$)b2%L~ge^T$qcC?CiZ5N!V}0?46g`>Qm7E?FEmIYV78=#d`)vPu(#ETuB36wdrbqk#~W-v zQS-AUUcHHH*e015kzPV3S1S>H^WZ5Esao#I##;-PA=B(7;@+WHSsSx_qO;COt!$xVOoqK7$KNt`T0=+RS; zNy^9VDM~zY*(Th%)gwq<**4gt;|K|_2_IC=1@s$B>Q{^8Y46hU2z(4oMZjhks&Af_ zfpZp)$U?C)l6{;x;0=Zg^P5#R;D!2s%!};r7WkNm46ly?$JNsM2`;fI53{RZ=##e> zCLF4rIo(Q$$tJSKCR6`nn1f#&YX3er)mv1%oqWXQo!HO#5d{Z7lhg9yTw4qU-ei|NHQvw7bDXcKLYy6b1vvZX)g4W@ zyU~M>43-;+m2b8O`^a_uQ)5^8Apz zUiD$Gt0Kyz*3%j54VwFMBg1$^u-5pEPK9bq*L0^?f&>4mbf<9ks`Tw__ATjR$pk@7 z_Z0T6_9gf}H$}m*C2^rkMwTfA{i}}%(w#LDB#kU<^V|k}g^64k5X0Nokuj4uo{qIcuN(Osv^l&D>bGxr73QwnuHc409E6WO>q731uHd0{wr8U3c7zRth%u+Lh zVjpwW7HD8|WVQ`MDeL&)Wk*n0xY1&@nrN)6c7v|&>TPa3h3lkKxIs?C4iBQTO%1_L z>YaWwE#j@tc{Df*VivB-NxB^CZoItDf*`#ukca})SzH8t3lmFGg*H<47B>CZ%=(~c zk%H|Z+)ybYBHdxK(DX=@{S4HG*avb!>o`Fx5V@pQT)+2BUv&sjGhK0%{Q4q>I&}LL zUvTMOk!O0O_5lG6`24WET!eh_I^|a|={XEJ{FkfW4PUJ5$lndKfP( zKbNI>LvZ0*O|6fcwMibE+6x=QMlOULyL6vo;6YFhhhx*TPJs!Fv>^L>i>hZYxiC{L zAl$t-z7D0bmA;NDliT%j;-#(Z_0h-b;O+DgE1@Trm*|vgvr`Qu4l5NPD@l>WtuU2x z_AMnNpiRrR=TliCxCg!6J0jwCcYDX?+GxRawnl%?$n|SFQtR$yx^^$OH{1Nb`#;s! zn%ee1#Ytt6PsiE~r0cW>BM$}n>ottL67p#dwsfnaJS5 zFDWmHi`L9!b)bne1xJz?&MJgbk`H~vxMXVgW5sG>wcwry)5Z%KM-vB!Ie(ixkMAN1 zP9_}#*J<$-`1~_zV{rEfb!L-MxiWAJHowK);G~uF9_|mkoihnXHP)y-#G#f)l`LYAcU_f!>-Yq#gSXt{ z!d2W&BQt~(T)5u_P@8TVWN!?$?%<1gXfPD3&t14O+3e-K;*B{xCwc|{?o-U#NVQst zOw3y0j(~S-@873Jh&Qa_`+4SqT=Z^bZqwUR^mA<&>WO(iIEW4v+MJObnQjQLL)v~D zFg2rMw%;C5j&{Jc-yER*K5eR!2_ZG8xk#Of_n%l+ctd9$lbp* z5Pa_oX!tHov9Y!O0gE!Wpynp=Qw7-;wD{*9uKabWCBBLVJ6!Du8|cAteg%DeRrJKe zALAGX&;%{po;Ul1(8<)}P%*_*P*$?*+CgIR1YEMqZx`}+su?`Imlu^vhR>gogqDm< zQ*(gsnY%~gf3JzrnV1^iw8`JbC%c)3$&a;n_C_axQ@*V#lLSgjFU`f)KDiR|M( zf6XKoc~AGHrPenKJN=Gc*as~Xm9--YLj5}ht**2tA3rDycrw%qpqdoxHoZzG<@(Nm zhJMSB)Xm^Q&St%B%KlxRF`OIvwnSriE!2*Mk<(HW(>}bD{sFJvo}<_@f*Kx|NGVrf zI`f!Df%wWd7VGzWpfxsPIf{#KtZX9Msz-#=pv|VApWmsgVA8DQ zPNm~<7Gx?^-G)=ccAzz}(x%qBC|+!*WKP8>t3#fOkEt9CXI>Pp#V^LV3BE?&NX4JQ zffQfAOqpOw$xf8NfiAi=I%5C)+mpA8h51v$C|KB`G@NGDb^&Sgct2cOq~+JIB8t9; zzy62~okwf)nIcvJO3YT;b#MdPEc{*wC%-sJat1o0Sp!?i4A1Baf5z;GYW|uxBQ6oP zD>xo|?-`yDTmA2UvE}Tx0^|IimY8KGyMQ43cvcQ}m|j)$%DPvQR;_UU)bk;z(vs-F z4w?B6rUQmz&L*c@e8o^jy)_IhGr^si*kO_V z_a)0^%Onczsdm#!6)V~NAHl9|$7MG&c0%MeZN4Ow5vR65-&IX>HV$UZ`7cC*jDc^99ep(Hx1- z$uS9}K$v}$WrxN5L~BPhJk_Ce15vT5{8OW7#*Ii6yQC`TeQG%Kq~Tb-EKErnM67`; zI?Tx|>N;smg?N`m>+)M}Aa6Wt*?IFGDFcW$g4Cvt=wk&>IK?pO4x;5TPs-3&$a36H z@*HL&rT^+_B19t?23{!7m6-@r-nuIkCa^`E`AJ|+zBJ~>ZmULC<(1}TjtO?(304Wd zforbDlqds+uJbsuYMoh1GAuGlp~Ztd8rgU5hVl~Ro8uKIGM39H1$@n2FH+IHFOY+W zS$t6ba2jDSY*;O`X%|hTJb+MNmdT#2c<^f7M@K_?BFXX&@gc2;RLTQj>H?8)ys;-X zL5(f}FS|Zb&C8RjxIS22T$xrD4|qR^ggNNS@qOiP+LA@In@zlO3V6*#wm_QcQVhHV zgCGucjYEx$iZTL&HsAVLni^!KXD_vKMww+khnw=-ECc77Hg5%?>JxUE;CmYz4TRL5 z&u#p=n%R6(!MY{l!T&|uizq9q(t*fe<9fp1M6y6E$)Mevq@cqRc{!=C5m z?k^~~IrK^e1tO)D#F<$ZHvW}N?O(fzi4*2O$0m? z;IvJswgboXkjj{rJ@TOUocO3&ikR!FmBvaqzL~+ zoB2hw$Q21Z;AyyaJtM2RB|jWo=6Yd$Fv?U>p??_Tt{-n8zOqT~4#MbQaA0n1p;S!> zG*{im*A@u#@b#qu}7!*wi7BTXM%QMhUTtikwpsQo#o3$txQOKtygV_dg>`? z!uj0wpT=n9-j~?r!yhHXA02xQH(Hl-x3IeNVrj~NATBhXx@EKEl6He9`|O1R2?0jq ziybLf!nP?4_c>X-V&iHhJA|$-T)|n_$6nV5hwY%_XHBtYdW1B7x}0~XwgKUjirB!F zBE28TQr}G6Wqu$`sin*q+f6fmLbX)WAC04RX}%49-Pz<$vahLb+gDXr@BSoo`!VN& zfTV$-_RZMb7ALEinnA;NnluY8IQJjd#65$GQ%#5r85vVg1RDw$Jv2xF2blSfZG^sQ z@oQdr*0W+z5)p1I0Y^jRm&N&zrGe0Dea)10rZHx!qwN}IMnHW7p&VoF{lbz4 zB76!_yrac4sO-ucJQciLB7&1$vfj3}j8oW@Gd%AZU+i{Wh-6Tc|I^FhcO)(~>_W+4 zUA*z9ZyvoI*>iP`k+&Wb9v>w#>VM)=%l3X;YEk)i_kbOh+44<$i4-QNM1f_3=%84c ztHuR3W1Ld0?;)DkIT!AR*rH%MGb!b?SCV&@l*XHxvH9yiNf+Tr4@BkbE6{Y0Q=NS4 zdcWP%G-dni(*vkqsWL~au%ORkE}~*_tkj5^F0t64C#hFK*n9;}nID{l)zi&Kx8At8 zXzMHs(TZwl81N@RpiVsV;9{&~UrVAk)__N$|4wd!S z5=|4m-!;W&aU~S?X!y{i5w(Y;YZxSkax}4)5VaSBG%Vm0rM5=$;IckUSO#CZiw=kN z%2vaA?)o+gExqpjDZ?jT#gKiY{wTyHAK?TQeWpwEmwcHvMb)TYT-IzbZZwE5^s^NV z&%)!8AVCQ!b@+_b7V;5^%Z^|@ZSf7y^`aAu>$%&rN0P9?vgQP zWGnu5_b#nY|Bguig#puVku__qB@%2n12=-)DaG|ZwzhRb%5tM;^x2h}hOFYp^Kwhw z*>XIinRbewNhdmM=;#QDs1mKg7d|%AA;nX+il8g#GQt^h?4(J0gsfSK+Itb+s+6g? zqC`}O7VO$Ya7*F{-7jmcZ^$y*rX zZj~jxCFz%H;!HHRl=Llvmq8!8(6O%hCwEvOjnmTX7{AOs{#ySsC)LB0s&aN-k&;CmMl!p8hia+kawH|9 zR-<=4TzDpmhZdbaIk$tgmp+SoPu;*+y20c9$bYYcge_3}5*gd{7lSv;#&0It?~nPW z_Db&8U^bI%U5-a2boe1=0tycC5c@!p$4)L0Ty$l*DUJAPe8w!Nh|=dCq)_VSIBWJN ziN%^#8`-iU+Uz2CfTsb(_8iyr3U+OIcQkHSY$)hBVQ%N&4RIvO!mf=cnWpY*kSQR? zMR~k8_ZajD8~SLn7?bTiv5m5Eei{>QPe&Sa&^JkaoLqlB92eodZ!6C<$Tr*@IzO7_ z_P=UWFCA7x>>QI&!w<@Dxyeqn1G>;8EX$t!^p$`Nt6Nye4}SAdf}2y$ zLFKPyS=|s!h+ahW=rz;Lp_dRtuNmy&s1reFwC1p^*(aNf!>l+a8+4m?iMKE!ML{Zz zo-f3*)h_5YUWDpVz5s$besIg`w_Fl&!4@5^R>aX=>cC$ud!;9%>92N2srca!IZi2g zZ%Ju*R$&&@bbcv9$p=d>1$OKgz@q#5V5PT~g4Arh@Cyp+@6c8a?kqTrCeM{vw${n- z>ahzKBTd_Wb`rQ>w6%EA3e_iCrK)7b>xaIh z1W_p3^Ugkzr~;|VbgSE@(zic~Xp7>!dw~B@V$>mBuJY^d>#fA({T5Bjc~X!jxC1299&70{p?2I!MibDh-AsFQSaR)WqpDFj==AK(q0 z715wuGi1)0m=x8y4=#MgSH|34A+H_|*qSk5#t3SpG;~%)3xHJD;=Xs5Z9=T(o)twP z-gHAM4%xN9#{@|AVNo?tlJmgdHr408{#cyIV; zK7do$T|w((m3X_ERk##A;#P*6xr$7bvN0QiN&hz91mOb@QA*^h8FJ$UUnQr5be82< zK1TM_XuMEAS*u%P1fNtHW!S#aAdB<)Q!$ta6R!3!aY)U+c4jEi4*e`e?0VfWPAf~B zElNpr#Wf+#bwG=@PEEI;v2MbA!vZJcmAGR*CEiqay!K!ci}Ie8gjlF!uCZz!*|WD_ z6GJFi)UF^|jS%$W+>t06BMzaVzQ}CiZ&d?sXG~O)eYV(N$zWHSi@ZxXwvK&C!-qe5 z=5b+prLp<`Hfcnt{|y5*$K;}X7LIt!glEL+R1zk}@;I@Ig5+^Of*61d)2v*>1(aS= z>JGQm^P{C=nxr<7l5Zrivk6!7raFG$;T~J%%=^k}=|N^bj-mG@j;TH56a2L`QO;n4nysed-qRYb_8LguQ=O$SnzW7xd|fUEwKyQueHjg{ zC^-6oIZd%rOpm$%-uHv>`#sDx zL=*LLN{C~3wJb5Ai$wTX*e3aO&!M7l`i45(@!q7Kb3xF58yiP?IY}$!VbhM^`={vI zWtNkDgdWDD)1{Fn5 zEWoE!BQq5-DNh1n>g_8dckMbS8*j&VJv;?ncqH4zL?XOFfA=#A5|VzZ!7@o%*~JDj6484f zkcx74hzaMh#)5pV%+VSl7a0=cfT`8kgGkvXFdI?9Y zUytG$ii+atJ4I(IQXDdMcuSZciFY5PKPgiPe*QUU%Nq5IDG~>VwfYLz zp03D*mn|i$ul*gX$qMb@!l4X>kJ@u_tY&RK3?t)fGacXZY}E?YRPQXHtoLDmc%WHr zL#yw({ozZd=)n9n!R`E_s0AZ!$|YSnD)yw*i>Tj%rKmDT6KGaUfFPr_!>liFcN8Rd zj6@@fCHqC5zz>hZsYcKE7n= zd3Rk+-k;Bjuc+AQ?GXOW)ghS{JQV%xf}#K#MzwI$%eHnmGu$#_0<*F6shccy1k%Sc zZ%%I52FH{DcIr%Dzgu6^rY_xUK&`4uFiuc(1Mg|eM%0g4YTJhy6+BNF$tUID zhf;qqk#=qFw?ap%siJ~31`-}c=lkZoOP@&YLo!y8t6M6r;3dU42p{IESr>B_L+y4{ z53tZG=6USJ*2n%q@bNn%oJ~CEcxn2TC2t|Up%ryJ#RnD7O6?X1UR_QREN^^|jLfK$ zy0 zWy!CT7=M!)p!3AE^ubN_;+e|Z9OA6j(YBYVh^*9Dx14EtCm(!|{?1U~K@4dq+g-Ba z@F{N-ly1GrjTjyH3egUECWd83NtU3#fPaRh8F?y%(fwX%n&ta-{M{DooaNS>$8AAA zPd4NcWoNl5L*vB;zJ9pHKlW*ys*AYs^X3lwLZayQ4IK)D@;P=o*J6q+DyYY$1MCig zF&gy#*Wzy-v-3TWm2B)Mt%WAa-m8l&j5MG*Qq?b5?2hqIPx;t&mai8vBkG%l*QD^rX#o1aqUtMDFuxXcZcVg6* z+QMGlMU0HqhtjXSl!#wgGAtMs#W{0VXvxmE%}Q7Fu|51G0ky&9csw~H@hXngw@<@R zMY@Z%G{B16iK5F0zndZfFTg-3;QJRA_L!@ovQS~v$Vyx%n;*n6!xqSIlP)eX?YMCP znVxsZ+pgTgL2UK7Kj5~EwSny;aiP|i!dAr`Th%``?&W%66dNYTckCJ241U58PriA? zR3+xm@9vYavyjZ^1(rEbKAb2~d;orZJ&}8yb#r89nwG`~Yl;duYLoVU5ViDb3{pq% zN>)y4Y^F4PDO(^szoWl$@=0HoP>38Y_WVn4P2x}Jl`)})QY#_5*P~OlJHqRhQIc=q zFU^jR+T&6Sce64*uiU|AAd9i{TX+>-afNLKM7MJF@;n7eidG-}NHxRr&vynzH(afv z+?w{j*zVy^k_RwEb|*PLPN}hRQK}1fh(Mcj8t=k*)y7oAp_Id2)mYQl{#h=1J!Cmz zg<0aAXSm}i0Uz^?H^l127K}yt`sy49NS`cn`!ve>1eyiFopa@p;nb4TJpS+2jlW-N z%sD7q@D=YX3TE!&6TD=cv3Z4tD&XS~Wt^{M5n(`3I|XNbbCbCjM%5QGS&d${7&f!7 z&&814Hgpq_3x~Py5-5GL?qf}I=G9?-`aphk2rn~&d(rd(Z?3OtA_+d%rE@PaP-VF zi=GX8bt2tKelOi-G1eeE=?mUNwIuEzt2M{XO%YjndvTL43|d%Iv-Q)dG{{XP*aRLv zKF2+LcMI+UE(jNdgoFe-%-3XStjo>^T#8NvE=A*lfL9Y>ik!HrFr$>b1hbs5yp)8v ziYk+w#D3eKD2J5*ke>x0I#RrWpZ`TUN*)i82fhv)odf<@7Kmzqb)*sqRC%L)o~r+~7`8Gzkkjhl%2o#VAlA&M zRi=Ys7WuO>7;432Y+w&WU2wLtWHL7ZTL8DmnZQsx$f>woFt~~TC)|HX7*8YtPU;DL z0PrZ_BMw6XkN8K=PiZ_1i4D+68#|~C#LmGCVt-U#hvTd@mr%+KDA+U(kjWQ@^85cE zB_PqGT*n6%pgU5c(2sNvxTA8<3=FY{8H2;Ng6)WkX@OdYpTj#Zzsul>_5Us*ro%7+ z5(Z!(Mu5v{t%Nl?LyTYX2TrR2{K=6L^G}U`l?V_ft?u;*X7rq z_Nb5!v5~?Pv2FwMIY9Vd0pTB}vi@Bto}9^|;$R0cuo8s=v9O%~vXoHXXw`WD zcwLd`fI|KT2fi64pNX#uwSfF(MqnIaYcK=UJ_2Yv?2L9R{d~AmMkmWXz+${Y88D_< z;4UvLGJNK#$O?w$5U_(N)CdAT8MzJKzd{gb^H-qFurthyoKulccs=>Jf|n6W)>{JD zkpMQVl}hEEimd_x905x+*Te8`zxU z06wrn5CDEUWgYzo6>@^k9rrA+!nF2E<*B%afHMc2=_G*$X^x&0{n*~C9LHf&HZ_`{qN{@OPo}Ez(TwPSRd^6jcVz< z=>NRee=^B&i32@3Qr;e%N@8qg3G6}tkhy9D1js`R0(Ak)yT}nV5C|UxcbaDWgD3G5 z7O=j5#Sa@0?YO<~_vDU8V!)ybtGrco=i&OfW}HSqG3z|1i~<4`5P?94 zS44i71*ZQa!CzOt(={xW=X9Q*`Cta8!P(;e7+&;#3X*mkd-3r`FP;rVL|B?fT- z7AojJX}ymR_JL{MMFLaxzPrHBf$Qd{Q8>+w1G$sr8egkN5fT4h; zQI$9kjg+;Gqk}N`(8~cldliQhX4b>G(9*@KZVhO=EwIEKriJ@m3T6J!G$(5;aYc@Y zKd|hg0xlFR1%ljpD1M6R(3P?_aWMUp;YK=+{u@DC@@U(VUjlZ3z76zl#j%i%V87cgDeb@{d3Srl>*I};$O z)=!WA5QS?IrT&ludQTpUF(Fq?V)F`M~Co<*PnF|j(#L3t>A zzyVOVGXt#PbXfQ2$au{b3DEA@*#VUylC&cur;rP58`Z1$06aV1OOuh?KJ! z)Kn$dSd}3L|E5qszO=~sIuM5g>-uwGkYP7K+BxR{=l+{$j!%(SVa!*e>{M)Xz_$LF zBoVLnm>xb$QMd*I-GV`jIR}~ukWgL)>Szas92%|&6zX8_U}s;;DQ@TH{RVD^D^sK2zG4S(2$f1Oe(E1Q4bgE~I8 z`#2uRq%Ds`A7~m=hPY6ce9ms6c7;&pqsDv6O)c6BtIo6j<7MC9xdbpi0i-Dj!-$9d-x<%4=cW=%5-S-9qyq?30EYAt?tdmdS)yG=eR=l) zCq)&b(L?Y2|18lYU-U$s%Yc4T9q$|-$A4x#xpOxhzaoAH=GG)& z0AY{%#&4fVb*LvN!}eQazOevgmIcU+4rX|+$eat7v;_Bq-s|0nM8>F_Fy zeEZc;#dUD8`SWlRg}!Gt2vnVyrMpK5^LoM$?jQ;+l=U$ZHH9}y-oPZeu z*40PTJ{$iKR~cdq0XE~{6V&Z=aro(-O7Ju1!Ev{VbUMc+7#Or*V9N&U(GQ*j?N<~7 zWeC^>Z233ma(RB3Jpw=n0Y(n%HPwU9ZtvkNEo^6J;3DE^X8Bjg{%F2uSm*DR)!7_k z5Mu*JO9$8#md6EkG?A|yj27zIpPK^aK?>F1(nuetKAP256o&ev3yWJRzCoAd7fjq}fW?9eSbn*0aW z)F%DU<~b=5((#@gO^pJ(kJ}754+F68KblhLIQ-EF!?1oGFR*(&Wr9B|+sbjGqfzQ$ zA3ZC_osZ}|PZf?vae`%W0-Vb8bmtcfZ2Y_9eK{I723EUjGS6oDdwi(lJV&n_!JfUn v%srducu1P#=tti@h1IOdnzPaWxWuF=g8(d#Admy_uNLqX*jj)U6ZHQ88@*)} 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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

?@Ib4$R$ZbA6MIsF(ZEk4Ii=sfRBa)&Yo zdo*%+J~Z_4Ve2DlO3H=gri+l&z1J@juzqxm_xwRViQht_&1OavHJXt$3rIeBbnQ$= z6{7tuh8?B5NvT#b3&5vtz!ok*tj9nWQy>b2deChCqnt1&-?N3oU%RV;C=`rq2Ep0` z$9|7Z2fL1tZQlQu3vZLdQ?AdgU_}db4R#CPuX)T1T=XAo-N7Mkcd68Vys&*6L@pI= z*@hY++4Sa(UH8u_x#*i0Cow&F!B^KqU*D%Ax8KFrr)5h*&yr-ypzZW8wc&f$@rh)4 z!9Cdi7W@8o)klls+xdFV>Koblp>m8_G9?$uctSXx+snV@u@q;`tNH8|ydk31MnDW}cEG?&A>isZNL$3fLBa#iwuXx_Vb5QSoS zc1*L^MR%Gynit@tW2j=}4jgmYyDCao)m8jr4>(2v^k~|32Cr)lIXA>F%C;ahnSt9x z1GaRvG`-Xkk+z8kqSX!{MH%#>P=`Rk>2!-3qSK{mioBN_rFQ1|9+9Q( ztI6RR*Le7!y%9!zI8u}a2GcLz&vLLUhCEBh)8L}1t@g?e>Lwh`8TkOkt+zDmDG7A* z*!h7IDGs5jSEo9E!zC2GH5p8bM)Y>1M2ewL?T{E_PAf7|g|#3+Brny}2BWoW8nDnLxaQgd$|Krm|VgXR>c}%6|I(d4bwx(t9Qj4_-|)l8&$?59gZ#XM!LL zhnyFj&qCOTLMNq_(lCm*u0&d>b{gKLH`kcZc2nf6%6s|veBOKp!3+kTvA6sJ9(qoFmReBVj9$vK&>HQspEPRH07 zdd{(FsPScw7($NHWhHJ?2?{FDNO-#B))CNkUaa+srxL^|v_e&cuJ5kFSN%qhNjl_YH(V{ISF4U#Yfz+gz5TSG+I9hWsM(Bk5fN#dx(2?FM(Eb*tGz2Jb{J zY8Ai7a6iHpFThsiz+3L~3ZLFncL=vHhE$GTYFx`LF=7_pq4R|==h`&^J$9a%O>-T0 zEJRwIV6*?SINx8XX$7%D^XuO7XM(@`{q0LZA{tg8xpbt6l>@)TlQ@Q6i=2>y-#vHdPcQcoi<56xTBo~U;b0!Wj@=!MKa zlLa>@J_NedkI-7@$HRz57>PWh5_-h^q_|Xm*Vye(>E!a;uyM$G;NUgFLfU95^gD$j zIB(fSpgAh82AUT>o<~~Vfo6&*!dS!o0n#V~tTw_g%AOs9|5DQbRaH)@WnP*98K)CE z5D@qO->Uk1kegP&&_-Rs^POpAoV@ghEF=_K2H)?5q18)NQ`8o*WeP7wq?NR0xrVJo()6J30FGQ@z;1i0FcT|@LbAaW;7tOF6 z<@1v0&+u?m2l-MNUUz^)Ox!*eCau$85B-uFj_;F|_ZI7a<5aPOf;9Xr`T-;#JQq#? z{nFr{$GE7yW}?(UJ8Wb?0$W(4@tbK_4u>lAswk8*iz8=AmD|wFRhOwoWJH2WYd@UI zJlQ$ab7YH4Lf~V!o?IK$<)-q4{2}X99LY(QH*ZnmxhOHQo@`V3v84^WZkb|}O1=hmX|@5pp` zmSmO)wYh>Q$rzC*;hoVd0@!9U!^G~|t<@Ac)Ec1@+Y*yrQpyum%aV<{b6s=}xopuQ zaGO_xp=`<-EHYiYMo%C=*k$#S?#ksK#+xm(572mCPNDIMUu$UW$bWL2$s|~7?hyv% zn}46?@*Sz**3)gQ+gz?CZWDnrFS?i&ZlsIDF?kQ(*yqe=Im5@qG+5Op+HV{gP=%R7 zK;3h5XlJhsdFP+r>-&J`yF47EZ*(27gcn!YZo|1xeROt!Qsy8c0uvp;$4!j~dMzqh z1rL=`=9{snfqFgwjwm0JgX*f+?n8r`*)KzeH`3^HM$R3bA;ldIf_+v+w}s*#Wx!-Z zi^;B+Rn6l~S8Yiaig=$*cT!^_VG&c47lqzhj9j%`i`7WhUXHFTCbsJrI~4k%@tGje zEA}zzO@dszk4>N#e=DtnQBOz|CJM#g{%zzoKUk7)b0`WVmGlGpC%S|5fbG{SS+|U+ zuBdm)EzU!TkHKp~7e9eHk}oW%zWw37Q~B24!WbVCf0 z4yDXRiY;W&ppNd}kEC13t=9)TFKMYr$#8UaCCEr?w613bQs2&I?0({VUhYdZxKc!x zy`T}!OjB{K&7JVy0R9&NNj!wqD+ZUwrROl4L0w`gWZm8bH&!&>y<;Mzc`VJP(=RQ_ zHYz!z>OBzdv-C;z3fCdV(PYmJg3{w|8U7@DT3sSC)i|QL5>=uOcQ zO*X7*V&4*k(Zdq6d+!6I)Xw>ALSK6FKCK$R1%GazQ$=a$YxP1s-@; zr4I;ee1N_`e!GsKDvlId6JHL*8#i+5LJ5&z1G(d$Ec{L1LeLogLN5TZJKA8HPYYU4sQ7@mm9`*) zR%d53TNs88hSO%O;0c@!eR@*@Ie_eb-}Vu40jqBklW3%2=k-|PiS@i-2dO)W)jJIZ zLhL;-``J^A?=l^>dBk}YK<35d-Jnm>KU>p7U6l#*UkP8^?C0v1^Zl_vQQfjTqZYld zkx}0eSPyfE96Q+0z9RxL*sz_oI^ckG#d>C9BDJDHyPY=3dssU8=T%)XNQ}bCPnhB> z_Ua{!gjdWW2leCtv#gGI_?D=9V~$d7mNGQL6k8UZHIq>M{u56+TxQfG*l878e@J^X zeJe}YT@h?dstcCA@8@iBdEUTmtB)iUgoK@0uFeWW^mId!3=9zB4w=vu339QM( z?8uRyo`Hx9&t12v#~rC*qh!x8r`%K*QDaEm#ug09mQ>K~(qfQ` z^sIPC`B6T`d{lBKmcGs*TPflvK$GxNRbJznD|2D`h#({B)QDegzA-ydxH@FS2h+v# zdQs6fWsqtDnovsZYI;S4*CDpI%BpH`%VfoLM1K)BhTLm}(B&^nRL;>;txQX-usC&v zsxHMfjCE5V8tv|p&AN|NR(MfTZE%!x9LYcaB?sxMwN~=`5!PTJG!wi*n+>>d*^FMHs{u6ipyZ!&WMF~;h{)=() z6&hAX2+ap=96b_faR4Ctfku-PgUiDJMvoz~N&JotlKYjv`6k)q^YI!o$5}pcTNyKT z&j>&#t8n#b2Hbt{1;@r$iw`UX9!^|@tXjW@^}JEK*nXZ)y%lmqD0IX%k?u&>s6y@uZU_`!T$2F z_dp*OL7gEDK{$fYy?CaZa~arIY|zJy3XUFV-l14Spq1 zdclTI07=)ezQE)fYmmKZqXX+o;jJ53J2U%l-lC zD$m;JG#>l0g?T>_S_K5%{G3XbZK~|9D>ymTd6j%Gu8N zLTPNv8Lmy$?NT`dvB0XomP@q3Z0yl1D;zhCLvOZHmDM)Q2WNn00|FbpzPtbh3QqTQ zdG_13w*~vvHB~sf8B)JBoPGTeWR+EBcIf}%dc~2vWT#}`G>`iWH zn?|gG=peNc_Hw)t*}R?8-fRO6Y<}<=H=0oYA)Z6^bivhPRa;hvZsx|()OLxYMwx+_ zn|1dc=x-i06&I-tTcmCL2Nm z`heaztiHHHeR%>uKp+@3%Ep~jdmXO1lk4DibFFu8(KsSh@Ob2_9$m?W3iKgwK*=Qc zD73I6-s+HC^^o9nk#yrF$R8w;KLo9b8^6GkB2H!1*1_44tybA%bBeBTwG1hSRJ(5E zTo!eTSdmf6srFo1c2NJMeHZ-}?jYlcTSzheBejcSGsXbPxOX0xLA#`IwA-WN0XP&3 zD<1md9D-5x89XB8FJBJ~D!JY;or=d{!n{+z|Ei+DCanBJS1@|OO1cbyTl^17{(rr% z01LB!#VwfrvyxQ*_5l=*52?qb2iZp>wL+$cf~Fk8Q5XHD1UCGQPh*lfh1SNcVl5`0 ze~2YjWDeg_zHKr(1sR=`cCV04{!W&p9GEV-UpI6M(4|8;`sfh%p+blesi?R2r7s{EVc! z=Dak0T8c$JPoBe6c2%Kpw9jOrsXJ)yNTt|o9O6C=NQ<%wYPbolI6dmUwGthUHJhdu zOReQ9O=zQs9^10h7|5BGCfHnvxtmPKk#?=H!c(G?KA{*$0VWD^aS8P;-Duhb(IwOj z&0&x!O{(=mhf`YyqkZckdNP$cUFnXCgmrul)j8H^v+j&j zM%duQOkkAK=_Z@*bS-&XA zPhP9ctd2?G#Kn7?f;N;gNw)+xF=i2FKyFBw#N$m7`5ksaPv0GEjH}DVHv)%LG2U4c zj<5$(hFNXt|3lh426x_WYr?TQwrxAZI<=>! z>h~$1QvX|PUF$*;nA^!KIdB&-mNi4BOJEwInOKMlyPV(#ZI$6@eHY=W^9h^-JI#oV z>nm8K(6JEn1*j+uJ19*|XM;-U0?MpUw859)Bk=8Wgp&sWD){~bikwFuau;|-J4Lq> za~^ndh-pI0V+@=KuaN{2yV zMhoHZvO(Ng#AV>|}WFn<)vVL=&j+tmHd&C4S!09nn>uL)lk@I{P$GEL#(oTlcj zc8nWHXA%|PwMOBR?F7C9ehSN06m^#CP?1@ANf2=?k9%!!_9!L-|5Kf>!hK!QN4;cK zDUSKi^wBt3eP>67)X*NLQ|-&7mB_DDG-c5K{NVje|lpAHcq z2i0ktQvQ6o2QN#Oh~TRrm*n5D=`)8gkzR_#6097SZ*KZ}MvvWW?grkD505@)qvvApCS4U62>_JYhf&Ifp*IEt#-U zUn6=?i7x~29(1gjsRYXaykucAIzN*%A3d`{A&uR^1j-KWElN1^gqm@phQxZ}Yik~K zspAQQq!CWDiB$sRFmkm-Z(@3{@;c)}n~f#~*Bn){coK4zF)fr#Xm3YmwF#r3GO}5y zhGDGp=w%sx_sV>0$dC1dZ$N~NE}T`xP{m+&1*U6YqAo5eH8G3Og!F_>#Mf@=LJxxa ztdqo|Gr0kTTg?EyGDr#-0a9R3ZMCG#z!l-VzHw%GR#`D*FYNd)HKd&$I<~ zGwvz0Jpk)B^{Nj48gR`PU38^zYW4|S}MzuN4Y94a8s+#agO-lTh}T?+Q^yN~7jIQ=Iys{j2c{o@EF%5h74=cHyvh8pM1 zJS(Y&5F$3%&h!eWBS}LLAGTz$ma--jSS_F9w7d7bv;6S(;T6xgqmsf`J)iVG(P%3^;f5xkn($yC-8TRSlp>8ryJCxM8_6vb+Yr5 znH}}z*EySqBvX*OTe_H!cxGRkwe=X8hL_>YZE+u*7j57|7M7gOt?lF*OX%O(I1l18 zq*RnPYL);!e@dx+XIqMnAxPbGijAep714YL-9x}Q1wtrn4L~dsClw;5bMpw%$n@#i z*^O0ALEzBKqP_%&3ctRg79dm-;JxMK!xn_D`us!eKe%4NiLSqkj!ko)sZ%iG!xy>< zg_DtZ(j`lv=(kY4<feerfn_i>T%|jmmX5sxW&X#`_>&^!5tzLg= z@1Xx*hK&Cd31_NU{$;x5Wd)(OOiKp})$~wUSSkf{;U`}UlZTuuFEAY^zgBipw@&&a zUF+~N2<^`c+dB$}K1Dv$CxG!gjMwznHhAV~YkFV+JaPT_{^qyw-5b|YhWq5o!IBE^ z2heZq18Ijnfu9})dEiWTE&0Edg*bVXXQF?i+_g}tWS6LRb0;eDD#iBYg>fYzdlYTk zgISK%#20C7HndJ*Y>u_&l~k)>k1f4SlAzy{i4;DXVw zhZ(uqWdOmiVd%5Fk3$CaT17qYJIsZeUE>n0SlxB84bQPwOF9{KP|mQlV%3XVHaQl0wC6 zt}ZAX%e}6w0Ntb+dM_HLux+L|iC!V`5XmSJO!SRlPcmj#@6ul`%0L2rl#(#!+fZ5N zHfzG@!R5ch@!n?UXY^6a&vwdYo>@rSV4ezrS z%d6%W;k#~bZ(Ow(Fa=SKcobfkwL}UU<^-lB{$?qpPaXTu=nvgngnt0ktExm1!qnrD z&uI1SxM0p$HmlSOnsx zz#tt#Q6CbEE4ZQDgk8B54?iBl-0`mVHx9l=#w;+Y=#dah8UWt-M27J=^MaEQ>sgY@ zow4H2S{Gb}si9+I`IQ4)ZSp|}rdB~4)JI?se$o}K(eGqK&^%IS;lmgR2c*VZWE-W7 zW>TD;#}4q4{p+E+ljGxGjJ*EB@^(B*u!p{%nU!xO;^%)*vHc&gynnninF?Cw^S_X} z!?C-Fc+{S~|ALIxw@ z_@nobApWQQW$O_x=^roeFUa3maQe(PY`ww}08fM#!Yz0^zTLuG2`@WYw$`HYiBUm} zbSDd@;zQCHG&T3icYM6bLVSV0?pkL@FQBL$+5~)StYO7|8|E-yB*r7G0RJ4tFolz( zyvuDEy)vhF5D8<+f{B=jvmN1w%*2gXLYkF64CN%$M?o2J-$R#U^p-=WocnAcn|}Sa zDV5n)Fs4r;V;m`*jPo>3oOC;ian5HbtXj&fmn%6aNYgC}{1ARw8>T|b<%iCP^Gi|Qc9=ZOQQtg^{V5Fit@mF&{oM+Fx?xMvNo^Fp-U1Nfyw zKfF&$+ZQYsTN8nzjaC~*)!0pI&&BrXyS;~`toF>PWU8!mA-?m95$>!)g~hOF14D@H zVdw99*4Ri&#AbWO)tlH%HNk134{<3lXs8C|<2|)lJ_K>( zN_jrsSn-tpf#lq ztpOZe>|p2lZE4p{l4NkRYiZY3&Iq@4g3uQct7^`*ya6YN?hbSyi|!j}>|1gmgE)sUf-;TXQ>AEC7!W5aFxMxz7@oBjz*TP`G08`nlFU)^ zROd8pVN0G$50Z{$FoFk)cx0f}BSI1#I?QP+$fP8n5$aZ2YOr3I-9Ix%rG`AFm<}C4 zVr)L#&uV}cCt+7o%XXL!H#vpF;pkbLm!5};3LRuE_4Pl8H^~IEPso8 zKzr0E#j{kzD!t2Lj|x~0ksTQVqB28&#?^nGkP>AsVK2sujoIoK{(g}tS{L3}EFG$u z`$$!!)Wy76Aez@opDZ5!4!*xZD({mKW((Qqm@{q2F+Hv&0Hp!tzlcEA;oun06Bk?x zt=LHF>;uoEI5N?eSyc9`Y3cYyMPkHGQXx+nP_h#TC8U)7Xy9b{Lni#7_RzdQz67e5 z53+u=-)PL*bH#Rz0smC(0Cg}gnVzuR+Spa>4_DxjY#(k-j(5pvvo zJ6Oc?)d5Fw8tLxKPeZ8a$Un3iw&h657Qud9hZIaQ851AWLIT}MJnPnm=P9+53EEoO z=8wgp1DK0?nL$!5vVF>GdUag>bS5sSQ>D*&L3BG~)al7#`CDO%9zuO8H(YBc39hr4 zT|w35&p|=fyBhCZ2f&w)o=$7;Vt@40M|u`8m=M@3YQNSj-nvRc)`*EPyZo=ro^iEv z8is91F9e$~VjJ#@Q-ZGcKy}}J!|=YjG}`mm`YM$&DWet=5IZv03MjmLOZ0TSSd^5{ zt@w>-DrV21UZuM#m|l@vCf`ceA?rzG;BSPB!ZTfj`5o7L`dVb>g>2;m!+YjhSP zEDF7kBDmS9f5w@JIotb(q3+=_i{DN# z+4!y&k*(6I_mSz!cKv>YYnVhy)0RY8>FLrYR;d=)X-qvR8km8OsFHYNl+aV4WG{Rq zX-Bu3Cyl$LoFN{r<>?yit`n8?nVp+098(Rj7ZfOn#Ku;C(2THF z5h{earABBo=SB|@^8$>Xh|2D%u5k0dB^oHa*be9rt`-dKk?dnNSr(Jw=rMRMA3Jd> z+wsZ1)9t;F%H0GP?2)w*Ikmo+0*AW^)VYLJFm$d&{Gkft$p&T85O|?rz?_})9K;<0 zO@{@EyOdz~o%Jp4owBKlPp4s`ZP1{CG5=xL;F%<8@q*eyuwn{V^uy@0qlPD3B}0S6 znX6fbysiMcriOP{eivf`DIV2t*BQ^&v|Dm;30=61{>Y!1hKLu&R8(c=JB#rE0Cg(n zj*Mh@u{=7MZE}(tHG}H$q);v<+nAHd&5v3t@J~%!-JiRHXONFU4-~Y5tvpEcR-2ac z#z4>0 zP|C%-%n`JW&aW@)o_ybkr-oaijhlgzf#L%2wq^{PTm``rh$+Vuv|7v{(4^~(g*jk@ zlzNIA>;!OO87%kbfIe!CG0GHXZYaPG8peZ_*$$&`+;*HJU`45ASU-)4V(x}SatSnX zCcn>BR2>ooLJx;H25pDBt<`U`a)6}fnnN>6;s*Z;*Y}d&5m!qq#N72wJ*W`(Ddtdq zG|jjD+D%i~$9NZZR$EvWA{6d~4{nxYed?E%`0?#FE;TS4D`bmQygw%!+~qn~%N?bU zyI=6!duVs23g9|nQK>Ih@fGk6aUX^WU2=Pw&)R9~#%9e+-nh;7(7 zeMTGL1_BiZP!x_J&_a~7+$1d|MH5rcfYF8&F9?}DMaUhVpzD5t?U42MefV#ed^0jO zG71;O@YhwWS{G3S?DIGLit9lBJ-Hv2{jGhFz_2A>UUQNx>`@Ib+VwB{>f?e{(W|jDD$)2-eE%tT_ z+0F)pu_2p9K>O>GO zI(}|8X0T@%D}bYG1-ryE;cOM$mG<(F_Hx%GmV3;s3w?2ls@zOwjkvr|dSy#fq#r{! zWg=R=ck3L8*?5#CZEkpdZjXV;SLG?`{NCJ)EsbUHt3uf< zn%@n;|B&eLcLVVE1m+*rWnk=>bnh?ZpedVjXTE{4UQtc>(7{2L!G6(DQcAEgEM)=i zB!WKZRA~h5i9on*0Sp-_5UI45loQ^smrwhmi=m@6&hNFV0yYG1!dcdh!L!F8^1qd< zbm!dGzZ9?HZ`O~L)y?EPk^koaCU9srj*Pz$hQ~b~Ktzm0ujYZxo5U4a&ZAact;*G>eqWztgFr0)00=1cctVm%Z%sIyQf|KE749~t5TXc#<6_AD>u^%PCL8yr6>i5fwpa4U8%2Xb!0Fs%VR>u;u& zrmlY(nEka}SZ}9UeBUr7#CJcS{2%lJ-{VU|JFD+xTRsa5ssBm{_+O2i9kG<5gNe1l z-yNN@u7%_OQsyd~f8&?vUea+!0+_T1i}!k38q_<4fME+s+JfXEFvx&%-D`C} zh-|IhhXnHj=*AKQd86Z(&g28s%0!($s9N!6oz?LmJ^JAVel!-!00bF8TQnW zb?~dX-kU5X6n5L;@K>|>q!!(OsLRgUV6)NBOa0#NCohKODl$=!yf2Bk`Q87f1O{;A%m3Nm4*xx=sZEX_D4?XF@S1KF=J!tWg&R!OfDx^4GQ- z%c3ZzNlXoY$RR+nhf20lqY0yIAt~J9Mhz9E%44cVAxyZD*<*FwAZ3pq!0Cg3^J8Qd)0cU2z&= zlA6Mbs4=Fr*1&8^i7P532k-HQ>|KLaBu18ANXd-AD9UD$Yq*Wf;}y{??=@52lxz4l z|I&0642YqBqxwFsL4nuGX}vPPpH-zW z4LtIdBghAe2{&HC)LD3Q@vslaDE+!PyA9c}FShEKKgwUd$X(M_Yc}PadZ9OqE~uRw zNRGYP>`Y$&1%mwR(o$81Cjf^50C*t!2AclgF0FsOIuq)UE{Y4^Ap?v|bdtF6bvuGd z2!sK|=(6*O5Qw6Fs`!fVat%(B+P&TTQW=u)6{u&lO|+I;c}u`ciF`h;pdlD8 z0yvq`_F`xcX)j*7vuxJu2w&rammh&8-S&*n9b8^=s9q!8$^H;~RDmvlbffLnm@X6{ z;;veu#RmK4vmRPT-b5ka-Qn%CrD+#Zzw=EU^A^+f2mUFDFpf4wH{AYHFz9pa`)3P0 ziHmA~r`a|cn3uXwHYfugFWYrE&^u#@-8*H7&K?_>7uBCXNLsk8*KhbqyKsJALcre4 z*JQxn%07Or>rIvYv#JHfL#J}&TBamygKH`xh_SR(rX-6(dYwsre`ru!j|l@$sCGjq z+oU?3nAMVx6BD@|wQiiKl$#e%#mp#vAOro#&!`dA6<&ZioKi2|))?gryrKI3)ytaC zJg90}?Ytt02B>x|W0%`R2`Q&8bLd(b#I>+y12i<+D+L?I>f&Qnwe0dyWCSa-+DfD# zSVb`PYZH?~F4X4MHgLk#LqQ3RG@30|O-WeBS#CB3(_^OP9YsfQ`ONb&orqV`Yf#74 zYbokTYkk9YRD4olmwkmg?%V>e^bnS3Y(^ zn#Rk|D9m4mI&4#nW3oo5gUkiq^cxvdCLak=jmTrx9Ry&D4G!!{1Y0%NsIz-c#o}8x zkfKbEnas1uwKEwRsylXw2!(~lLEkkCR;G;KIg5%ABm~jvI`&sI8DwVdaY(P?ZqCw3 z2T@6Dth#-YR%vdZvzq+QByQQ?Eu$`v%;KdnkYePUs9R^yAjzb%6dzSw)~cS)E=*H> zrkSy7c$|bW!%nr5Bqm&wC${E+&i1&=C?o$$n!_T@^Y5K(HhLl&TLG~v@=p&X5z<)F zkkK%cs1)Fv_Z*d^-OQMWKEw+Q{b|IGVWJo1anP~y;B6l20KzW>c^1NAXVY-9{;dwJ zFAtg!tG6q}9$-m4Y46ulCQB4`cnpKutQ{|%vy(K}2b_!NDJ5Rr1DG!4@0i(KT+OAg zHxqDz9doLyPqjox;F1liKavR4TS>G#y`eUD03wTtARY!*a9ac#%34_AJVQ>jzmpF$ z!?eSk9Z|t{f~jN1MG{~~)^Y(EpuxToqcOlHq0OHnhBG|X#9WF^+uF^r>`R`d!C)%< zytD~KzO@N&Di%zF6Z!Rl1u}$XMS^|^+t+8KO=r##O}=N{pdc0mUYG)5cz5|zg+5Qe z#O_<1MqSi&YD62HxyC11s7Z{3VlOL+|93G0II&uNdvIt0co+i5MfrKWC zluSxEVR_7St%9VoV-5CcwTt&$9Qy4F5V3&ZmJs^2%>rDV&OY4OvEFG!Zdk^ZICVWC ztxY?zWPWrkvL9GmI#`lZ3YBQOi+i7HMy<`Xz&>OC zpu4_JX|JUu)`e+ZLZ~1mJ3x9Vj91AL;h(bXdnUO~K$%#gCy6da~Y9UPA&?1ha8;U-EE(%CzPRkQ~8Fo8#j zf*;%bJr4O|Xnc*mLhWiF0w;E3W=qKIYZPKp8YwO7hGOEW>ZY9VIebwX!4IPd>X8() zqB40^8}zJRVwPKny-HJd>@9=A;p(`u9A=eCJ=&_Y#Qc4=n6UW>IZ%E50^`I+meRn{ zT#K{d*|H3hepU7B`vwUkvB8G<9d55-cxTvmO{8y~u~a}{HO5^iiHZSd?vZTpQn89dSN^ol8sZ5=!E zFTm@uelQd1$R2+ubg1;6m^cU9?Ys!^PE~Gh1`a=6nY#wG5mma; zr}pAj4F$4H%Lka&V$CF8ss_per{B8Cb}oAVm>KlI9^Q^>v$(SSt8#LuNL#@hI5=S|Tj4)=av|$=orHo zeuQIr|DBQ1_9udlP{-13WQ`+7>QFB~I7Sv9EJrPZm-x9La{90g!KVL2 zh^d#e%loCxOX4Q}AH8q?95h4rD?0|uyJ!>+jhkTZ461sxA9L|csXVlirc&DpUXm9A zrWb$#Q-q1Y>_)sr2Rq|h;5y4^t`ZE+zy~rUy_kAjftZk&u9zzZ4(-E-uEVJA|MkJ$ zU>gfwL+7=03*0cLBI8-YBhN9(&3}tGIX>+yI(ZM^nas)sx&X(KjoF`P@dNCnMo7!r zQxgX1vlD3dlYsPNS(P9o&r`~PAn#3n*>nGb!Oa1KlNCrR6%5iZ3!*j=gIldnAfHYH zRaYWyK$^|@DSGeL8o_1n&4Dbvui(?;hwl=o7qQIBp{ghfjE!xpA-u!54s;Q?4I^WtO7R6FR;ro-%gy1QVHH+pxnH2yBeTW?Lz98%?asm3G)mEVCLahNO`KY?V3UrENu?>Rw08>(ceE z3^B2JIGb++C5|R@KXq*uUcP zmcIW5+c^GC-mijR!&h!b@%P7R55iKz=&-K%IIRpJTrr^@U~uf)ZcS{m)mWjoq&l_1oZeE5}i-lJ=+}W zTul54y2FX8I>5Wyx&-tJbYGS(llx$Iof-vB@m99|06^usbigVoXNjeEN&n33mW)q6 z+;qO4$n3w>?Rp`PKhwfAW_eHY(Eb#$(`(XK6#woi~%AfSA+0b?*>I|u$u<)x_A zS@&2g^g{MB>Jjg(XJ?suS)!#dny_{Fom`g#Vs`tS-D(dvnD-YGU(hMr_y0&D#! zP=g{In2bu?sXY{RRNtGUvO()jJS9}W9k$NQBg8t_oo!Bj^aq)N!Oa%yWJrth=muGUzE9$O+hoJEy;`|K4W!^io>12Df>y?um!JDmkyF0rF ziU{nM{``*Zi8-wN*lws(&pUo4zE7IIP zC^}k{3OG8>?t<%cLDm;V1{g)eZAzhr>VX7x1HAy+JKBFEd4CZ-Rw7P8ZoW~x(r;?+ zemEu( zq9e8M)sCBW3C)RusaWBlS+^;JsG&8`8Dz;eW)F8KlbEODw&2$Yk06?3(j5hB*{yQC z_)s(v5Nhd-({D!;?+PbSPUyl=fbD%mbxBF{mMniP1XZ{!pfR(Rvzug7>1O$6-?9U`_=Bxxhg@I zkTzAiI#znyZ|SVWl&_jXZC$ZWH+huI#nNUDY5^k*2sM;Ifd!e(7Nuf*S07Cv) zLTrg**c{wipX;J?Y5@4L2PwH76gUE6r8nB^0K}BSLa#ZOf{Fh8)TF4FcXC^Ui>|kc zKcrGW(i|P;cK4}dob50Hz-^Fr6hhU+dL zZ^m2cVWYJDFIHsM;4DsreIEJ^3hZ*q?=e}>_0G1|2q%4F8G2Gfv4f@=d=bw=83*Eb zGZdRkh$!|3z>8)MBFumgaZaJ z^vK2oYW60C7(d&q_b019QFEp%h(&4qxjE){Hs%YP*o_{A`@p^CZS-=J0E;OyP5)?t|tHazN!ZJ!0FA{6d z(IN6`|gr_JbznYF7R_?yn)_WB-ZH=o4 z4M=n6R&KIiDjX*XpZYbMnxj9$e63LbF~TBKEvb!)-Z5aS)H$GlLNWA%r7BX;@x_M{ zE_Eiw0#_264uWy1228TI<^n?1%MsRrdAxyT)v$p35N+UCJc3jyEC4{q(Mqcdg!;6r}jodycP!*QQgW(PG4zBwVM;lhNb}q6X=X_N_&F1x*~TkT79Mnr zo$6*bEGa9gVq_Yug#kMIH00QsRp$8ho>o>7Ym~(wZY@dzbP&`xnJ>B<_Os*MuDrbo zQp0p8qcifSV0f(Z)LxwBRn^avk(u9CVp_r?-F>ABcw%aqetGDln+6i5!Q3-kqsqz3 zO3?*wq&4R4RI#&?=EHLNRoHE9U~6J~59w`rk?^Op(_QBkf;5F?gOi*l0Mpmd_ZkYt z8ar!Jp~ZmT5D;S1wku@%4B-x`_Ubh44%Tx~K>7`hwl@5YCFE9*b7|ud&?1yacw-+I#=Q70( z{AJAbkC;kbZLd%?p?C4#YJ_}-u5BBOCHedle*MKixic|4Z7i{}2Q8#K&F3Rm^n}0X z{BkoVOdSyD#GbjWbbi&7fSitG0UjLA=XbgSTKigARhMX< z%!q#vctFX+y&1g_wUc#=KW6#6?UsBf^btK+6k63R@!W8-I97~t@+lWq3ofNVW7TcE zcT%cVQL8<%vpH6`xx2XnO$z3w8+y2;nE7C)KA|b9@EbDURzzCgdI|iTTyndOYB!r& zwfHzI13*T@Q`^b3e`4q`DFCx%J@8=2Iy)g`v^WT3sl$XTVmWUT8$lA&(>WS7vJjJb z*``JaE4(I}04f+98z`pP98L zZ{PGLDIC|TD#XOc^2?CP~`q=?JiTzu!(&|ufTsxd)Nihdkw5Huit6_`z z%AKlnE}-BDzjFp6|AFP+iO%!&q@Iflk^s9kq{C)U=($%4ZO6N}*OR7FB38y6#;B4fuM`j#<%1v*L$D+RG7Q0faN||cJB5Muu4;JS!mXR~s z%EOGY=N`x{T`oQdkZppa{eBW57*90asv9{DUgr0Ke$hQ$S>J_2EX-H4u=`QEnU zajoj&-a~)LBud#ySvPfR`o4HdtdDj-HfhC}g+!kWku_8}Xhz^MFWjFizk&1%R8>qY z8uz%$3qhgn)N_2xx|mQBqL(7sj9@B=s40Han;hhJ^7d;m@B`$NT9FBCApTpaJYrPS zj9x@CW`v-pc8g-!Q{c{*dcm^9^9BW64z5@;r@NJijbpNMM1Ex>rr0`?CSK;|j4-#D zQ{QqDbuE^6>e1IS^>>udQ~MIjvTGF>rUj@!dyX$4l&>!hqNJ)a!1oCvZvxA~AN!M7VyDGdn}XifgBQfQ zd1Ru&T9q&|zfG-E%uFy6S72&G;y3wGl?Dk@ZC1wh8XjjDRs*rfaj1qLB$v%_cwS2K z>b!oe{Lq8kW$HDOV7D;U_Lshao59;y%=pD491_tQs7ezpL@MhDMlx4|8zPw(WI#1Y zWT^VnceP#>+ZD0=_ypQ-t|XtRHm$1~?@xe=;V@ls7$7J`wjZIKD&>d=QlJ47DDhr5 zDqD^QHu!DmDPV*Pb`1&m22=Soo)#OOcuu1SU5b^;^FHIYCzVvObg$G3tqV z+xqTPs_}rWnxSuYnRE9L*Bcpp zr{BXK*qwQZfhSaNo38dsx^! z{sfTQCBX$zfTEzG*F2^9H_zgjVrx`o^=ge?SZ(|GX^;J)=B{UBX|Qu{pmS{G9{tkk zVT*%E+G+DL_Xx=)J-*p!w|j^T{&9wSncWx5_^ALXD+}4i#Wi|yO{>BQyqtMl*5j~} zDG~I}Aj#@vz#}-kb)^S7$a4v(V+j~N12|=<*`heG?0%$BX_BJpNWwZFw7u}Pw*u8Q zY}TF^!J}It?j%-4km$_Fd_NKKv%O+7o`>?$xpMK^rJ2RzBKNF0%(L?hB2j&ONe-#i z19l-66LrbJ&A|Rk3=d^CE(q~RJSY+K7yMSPVJI=MrS;B<-o4<+} zKQPbN%5*ppGX6$=ukc)!sGXF!M@9brZ{+N2QeWhO?_obT?6-3H|7LEcU}LCnYGkVa zH(BHFq5nVTX9LQXDo7&eo#_G8#NqOOq792pl*B$2&$G&Ys)D8q@Gl;3w37>vmmVZi2r%rb4y%pCu#US|nI);Ce9!VY^th}weAFZUm-(6*U1EUX)K}`OZ zXWc4&=J9)Tffs^@Qz{^W(5DQ8F3L~oAzB$P8!k%;$EWOL=8F1_`$O*Chpf(((iTeMXyUA#1lFxttI0=nxsrmCz#ERom7jF z+;W%hi5DMx6$RHugXhj_#z<9YMyuZ8C1w}Hp3Ul5(&8euLJI|`rc9RX!Q* zXSKOWjXYR(Qz-LPfRp~N3M1$xawx{JG}pCji7HW&x$?ob++>frSu*S~#GhLZ7Dize zqU+Fmp*R{9L25ynOZ8n6V9=(e0puZU)NOh~ddDcr5s)cpC=M*dtyA|wSE4U`Q{z-E zUdXHKr-^Bomz~HB?Ylyuo1tM)t@%>S8CAX1DPFpx*Y=>3i_Ff<+eK1SG9!wdX1fck z%39ED2lQ>lbowAzF5q7uGnS4QP`5)Zb5~QC`mL5Km&%0$&S*Oc*FT~(!%?X^(}H(S zMfvQePO}mO*|48nQT)Ufc2QMjDwhnL2cKl^r~-*9M6c< ziuVuu3n-{=-#5XhVhnP2sCgD_5R#FhFN*bN9mCexDU+D@YbyAD2eQ=Bn5;WSs?3Uo zW~srM;-CvI>k0PWkI21Zo$Wkl2Bc$AxKY`m;dt3<$@u) z4ebKH^a+B*;IV~f1E9L@Cf`e9A=!VJzrJ7STf&nOLY-={3pV^g(>S9gq?S|+&31;J*0^h z-E?+=82E3M4l!#J*u6Dcl@nBbOMTJ(su5NNYa&oVQ~$tw-GZHpn$$!ngA&o)Br&cc zzlX`Kgp)4|IuK*S4drwSzmYaDO4Cb6mRsB2+F{@T4mJ%5+}L1bgVbEFd8gk@UrC}@ z$X*Izt>Mat$6u<=@&s#WrhemAp+{n_i4KJsYKWOe7Id#UjAeL`#4iSTTXkO0vaE*l z1E>rkW5`&fgYXWc=Yv31*IPz0CK#FnESR>PMhYyaVD1Zz)h&;X1IyOS?JMh5lW?_E z%HN;xQtm}u-kX?vj6twTfV4){bd_#ci*UP@he5k=+M463sFUMT`Pn@}xp{ztLV;(v zfpz^yQd7iS6(_Q7vXj(h4V6-}dBO$XIi|VZ(uI~&B!hKM6RURf8LT_f#Kq|ge7hb> zdz7LUoEV$bDn?vpE@-sL81==de82u`uahBJnqb{Z&S*{iV(11|IJpFYubcAzW0Oje$r^ zE4P-wHC*u)g}$<2mWuY4vt3*PM-9^1b9#2Z`aW5aRg3Cq-wrI#A2d%!D7{?m(w zA*En*(%7cUcI+#a7BsOKt=|BvVaar1ris;gZ$Vy z{^k-TYRTYoO+nKtcfaFDKl_(Wn~7jQb|MV2^?mh*v)!b$meapuJ6i-9pKC-58I5;%)tjU?4)e`%i z-L7aDuIn3kK!L(3CE?$5L!`J2T-i%{L6zQ1m*~*yh^?du-Zp%XF>Xz8WzdtpRNKp0zJBRz9Hcs?>JqWPYUHQh z^mCZq`>yIhj<1T8%e2Q3=Buu}13k8~cZr%e0&r8$o<{$u2`~&RKs{A7q{D+(&udYB z37XASa2_qr@h0J*UMim?<_&x^4RWsCwv4!vcCmEApmOjkEU}VxY87TBzM@i1zDiZ3)>~RiBa4C$JslEY1(XA!f6|oW~FVb z(zb2euH=)pZQHg{Y1_7|Qj_mN_c`4&^G$#M?kj%Yv3JCdwf0`izb@*wzK@t1Gt{5_ zB#m|$f9))If2@f3ux;X2FZB%$_?*V~KD63NK zogZ2B(1elf#20F+A-(<#J~N4nie)+qPFT=-b^pn4ggLtG-j=-2S26I3N6CeZt3@mmTD>YX)kJru(*XndCw}UZh`mJ@$ z_~C@pcC+>VxYVUX$elG?BX!6}dGmEq&84FJeq@{f6JkdP7yPy?sE80h7AY2>6(yclL8;LbwHj19RHuw_ZVuzZM2f4i_cn^n>gS{sy z7c4HNc4EfJK}n?%-d3|bk6$<4Og{#b>lkjc9({95z7g9l$3|&Ar`JKr{@ktcPNzk|Sg1k)41@sQn_^2kes5tUuvkfjq)cP`JMVl07d`E8 zQSx>&$L+IX>5=VV>+}cm&3(fp9n&IN4FTi-RS*Vc^@(ny)opA$L2xU>wlmE-^8w;N zD2z9EBscZgVsQ}=Bbw&N4P6R*{e@En=)>(#mDr-SAbm$QL?^e&Xh;``zha7;mbtGe zL~f4@+eYRQ{h1$)lm@#z>i{?Q3Fo_gpSIB*x9L zufNN%bWqwdA9Nr#&>jz##hfHoyrw#1k)t6Zf5R)nsxe95bZ2F7kB@@O;ZV3sxXpuY zW0r`g3^yo8?*SUaNXJ5TG!I|UagOx9qyNw2?=PY)O4j)@@atYU{36@b{~wOO|II`- z$v+KJ#Y}8X91WaJ{?>f`UtChF6xSs|8IgFEyk?so6|0u0q4?Z^F>Vzo#k+FRkgI%R zSR3P5<78A{wfLaJy-1_nf4W*O=(j)BlF#|)5 zxu@3m6Z3wQXu7+%1P<`~J3q`iv6$-$>g&cZ^9f~e;tIrtKXNe?!*GWoL6AeDl43Ui z_(xP6e#Z8Sx9AbKP?*)QA|Q>=vUkUAzLY@>z}#*6Ex`hvUpSe^Kki>V=KjX8;_TK- zer>$h*T%E|zij+}7+?Ln=8%VKC+-;WF|JNzrEgBA&thG zX@?4(Zz4G=qOTu;N#?f#NJPLO@8ncxnvb2U>o&jcHc;k%YcvxV9fA%nv~mcQqAQ8~ z)uS26l!~_;h>V_yb3)aTaj|1Hmc^?B;iEKm(uiKJ5e5}{>x~hr=R{-}k0!bCLO3yX zlNkmYj!2msd6ae>Df3voFf|kC64cmK;gmUw85!Xb)^jtVq5+AGsrk%v%H0poG?le` z70a}C-!24-I;vD+5Fy9IbK zEK+QmfL>P&)>qJS0bDy!7fg!%_(lk3U$~EK(+WRD=M(vV=H*{c0NPlw2FI_h3I0+o z75P7JkI`4Unf=|F>?`4{U5rhH{$qvYA1h8jZ2vLVUZnix7omXkA*)2Y&JO#Z8{9L%P{AHO zDOZgA($9q5e{WUf15n;PwFi^|YJYd^v<_`Pn@$iH&e*RNH`%odZhdDsB!Gz_dOj<( z0mr+#T=Rjrtg>-xu}#Y!Idy2+ZPnSQAXE*^d1$4MZoAA#o@Mjda?$SN{XSlk^S$Cb zx)t1+2+e?Vrybz;?a`kuJ^1P&!A9E5}~t3o!NZA@xz?B_Pks0W>Pt8RGGE$|J@7GNBKtdX!3J2{=K zL?fbI#-RX?WgUrRG96YQw1NFSe)Q5QpZj1|OiUH!5{1|(+$@y?lTb%qHjWZ8wrC&i z?3h7hu6Ur&yxKzsq<0 zOHfPL{+FQsJLZ=azix@chy?h#n$fz|`fJr@eqKaq6qrg93VwwHOo*_y5Q7q+ONFyb zF{)XxnEa<|twI3Q$B$3>MEtzCOh{r>SDd*ZO|HlEczH1<2WV?wCX$k;#(-25oZix1 zV}KfvCX{ftr+oBkIyl3@BDz-n&4bPfaN5i??t)o8Km&B&4jtO{d!D8tDXzLUtZ zh|Op`FYu2t*L_z5zMn2&C9}(L6@6Ve z0$+Ky=MkzPu+$F@Uz#DTP$1i7%$~tF_~w+j3rwH@-}We_%x9;Q`zI>X(oZ@hzXf=P zCSe%J?w67O6h_T1U%l{>Bhv1D4#YW@SwtR3e5xJ zO-%gMl}JUL;w`?bnW-7y>t-8g563IKKI#Nx@l7Y72ot@tbk_vF2Gi74R#69c+#g5;fBlIt9kx3*ODkXs*Y z35%TamFD?!Z}Uz2ZvjkJnnrsn=FUnI#kXfNK$`@d(0%cr*_%7?3e>jCx#j)`j}ry2 zg84I~yzZJmqOJXkvs;BTKb1FiK73VI7T5z$erZ*C9-qSV2xRTf^J=~ytpT}DS^=uO z^fCF~dj1v-&D4KIL;rt^hKRqSVVH7d`XA8{7Opz`w`d^3pZ!NP=*fRYgI=k(msj{1 zqGLHQ6JEF~NUjE16!4a3qz9K?YryL$YJD~YGT%=|^=Wz70L)cn#{GYx2mWePVK5dj z7XNBfQTu9B`SJgB-~G!)_m58xQ48y@3BkWdMVE$^v$7ichpcIQnzXH-G$9e9zMo%0 zS`e^w97-=Bwt4mj4iU8K(!@~$AZuED-y2lkq*>LZNv&d)E9YHkX<3#`f|R#HwXh`C zw76zjfo|2I{ZaYVUwFQCyw&E!%<=Pia z&ut%rs^^dg1gX=Cp?5e|!+GO!b-jOtg8=9Fb2OL?myYbr*A{=L4KmF$G$X|0B@|RF z^Q9Q}kV8GdZg*YQI|NclmS_Ckt<58dv*)R zeMEiEDAH!oUjd_&V{+bz4tE5)cvxd8rj7Hom+~ry>IYCs+9UY}&t)$&$do(@rmTzA zCdfSZQ`zgH02LMtBv+GhgMfosDS1Abl7o!??BL?!ABIX%Pmn4D);RS`t@Y02Cyu6% z#@_hGi{w#1zZDVYQxVR6h9&UgrgDiYa%_TDE;Lz72jC)Jl-WJS+GM1av&|-MEdwVX zF_taVm6Y(Hc;G7j;vpd)O_DMI=noxraVaoOnR?rN7(q+BsQRl@yu8q6#>Ez~2s@?c zAdBHmWcc!3JWHNIBcr+160*Q9losj&zf>oGXl3a{1|%t|w4{MwzNjnNBLcc@$~f#^ z^d0M&5Cok0G%=^l_c$wQpa+hrEkj~5~cCA%(ei>Ta6 z0$Z|}>5#)kDI9f(CVCId&s*%xllyd5Daa{-mG;|}Bf?cf%+EFbgE3sU5XK*k?Lw{9 zox(KDq;HJakAJrNk1J^z7tW)Vj_>NGzyx;`#SMjRnNw|av5I)#cYTW)QqoQYJ!cL`KQAj?XJuWXW_W(f z1Od&$OV!>Yy7;Bu@B91dxT-=pKd_rkgm0GPz?p{JUxcp_A zi7?yFv7LbqvWonpv}CAqy)^Y#=;7$6_Pm5NhfP|oLU1d zg-z+dui(OqltRe2EkxE*f49ztqVNq_ zPM_D`P_47UQ=yNZj$V0mr0Ol%hpwKtBWc_IBSe!^;&&lehQfiv&M{2(!2QAOrX`1W zSAgD53J9BALCBaM<%v*DfZjehq;J7ai~Fp93fddOH1!LwZgIVPcdW|HK*xHlB9e&o zkN6D!9dt({S$j$jKP_B$l|F)7a&Q8no`B_>_5l8UdXVn2lK24j8#VCb8}@IH#Q{D0 zwEpe5xjvR8Xi~u@^$>IPSsGg#TH$u}fCx^OYS6yiWbp-Bo%Tcmpx;}5tDF}1_1SaK z^Kans=I0TlkOy2t_Hoz|7RY~`EgC^K8_ft*YK}H=*qqGRj+ifa-bi^h>yxfos5;SP zbg6fe-L>&fphz)YJ+du)oiN$SeJs96{x*fpwg%BNt9O!q{J70Qw?G5);l2rH$m1@V z2Fu|}I5{g8Tr$o-4Rzv|SGK!`^ZMus1{PacBl9G3M2I#M+C+z_e(Bp9eXXJnvuVU3 z(yiUeS)U!`*HJ2x*i|vn#okOz%xN7Iv8y76FwYgci60~rZXqf%dk|Q@4psE+dK2TQ z^OO(p(?4y1F6^Ct1oT3hEBhs}bTe5=KSr3dr!bsNIFK)kYg*;a^2N&w_*DIbGej!d zr#Nw37d7}bQ z5c%RgPAVBcAI806MQjj5uT!;%_sdRfN;|el_u04-;iZh&xW%sX$=Sa4d%3PB-?vwB ztao{CeA7j2Uly*l$*Gj9~=cSKqi zyUu9)7rM@6%A8d*m^Ld7!W!9-R-IDt3C^AHvCHG;b;o+f*rA2j9AQfOI7J(z*BHRf z#~1)=#_7W9$)$}KgWx%&6|cy>@N+?ZVp;ftJlxguZRQRQ*sBxFd0Vp?U?hAJ8NP|1 zL8LcGoQbA4XxTF_O9r;bQFQogkz#ov6(&1CmYSsncO$pPNCG0)0W=|s=0M1Af{bk2 zqU9PtCOF1BHkid2C!R@6am9jr2{W(8i8$O1!5X+r=@Vc>?QY8OeNtFLmd(K>+;DzR?a15Z zIAScdB<2ArVfvkI$u=K-gf)Dn$T(g=&c~LUam<{U%nRH(=8h#p^nGa}PG$ zS1G@(QONF+Wps|NKbHxAZ5hpO8Jqr$P4;qrD^6k@7#F+~5EeD&GJ3>{?gsJY#^$_h zjr;D)r0XuQWluE?4 z8~}UZz#!iPy+FSUEiojMYaQL~6qtCK5<9OArz@8V_D;4Ph#c~gI11&Jj4YTdik2(8 zB2ORwVJF)9LTQKT(7=PGzs1q1Q}EDqQj#vs|B)rlQg~XR(W_%0j*jfW-isH+xkeF``ETF7O!}g8nHhcwTgQT|ti%WL@ z$dgllXj5)~#~qaQwA!H5BOom$es_yoQq=4TqsVyJv^gT3K6%XYM7q~JYX2mD_mxm| z+Ye+SezCqD(nS0G7u4*pGL>-NmisS>LH;jjM(}@HrvCf;|j2=HTM%O)iQVQP8B2rTc0sLtWQk#JwYNy`bK&W~lw!s16xRe1Xi3S-` z$T$Y5RF4<`GDGqgga!uFC2IHupT)ucU&TWI1#S4}JK_t#wlFqu{wI-_rK z)*=A_CPre>WK|ymn<>+@0524kO#BwW8XC2%?M*KPhBAP#^Y*u?-R{xP__y=*Vq7PJBV zmdyZ*>00b?WtaBOtu787@pr-?PJc8y=+u!z!mZ=DreHNN?IB2>YUq>x5S!NM823>g zN1i(#7@kx=a5Qk?TJsDlo@Dopomwj@UPf*wB2IP}SI;|7rmXC&f_H1KfjlQq3EjA& z`l3=rfJOnXAXZW7nChxNuARrT`RY?s(O_jE@CwO(iViWq&kXz2un)n$F4Az5c}VN-Tk&dYIGf3-Ayhoe4ds`MdeZv{@ z!5lKFuDfLDd|rnearmehjopmYQKlYI$IR-jObuVs&_<}`-PTW1@wjdnkJvDJe43C2lCyJhF0^8n)JE*J=@8RhGr<@9by+Wy3CY`4n;}+Dn zG4PXQ&urt+evh;mM#X8svWU#ZW$!8dQ)>d3D|}M2UF$-h=a!gvyW2!uNrZCIKu82l z&YC}{U9(8}ETTTmv?7_gxMqf7Hd9FMSJ*jVZ0O^dupkKA(Th)>$of_pC)r>~I=%S4 zLg@ax$q6a9u@yZ^t~lspP<44wfuM40C34Al0CV_-YOclV+~#`ZjjJI~L1MB>a&d?G zRO8nEt|`_p398z7X$RRME!yhlxu&Y^QMTqM@skyp#OgqRT7u`V>m=;XVH!d~ka~?e z9<-lqnCz$u>^ktacpO=_u30bquQUflJHoT_v) z$J?D0x)4X?6I6Q|g=lA=-S0bud`#p%fre=b+!z_H%i0nclE5>}0gZHWbsfi_L--!n zKo^Q$HSbbY$2ABI^6uKjMwayub|4mN{Cfn-q-*o!m$A?0O4`_;jymcDc<1kurEYfm z(?XqhkZ*{Y*cJ;je46BV$7P@Yq6_uc!5s>oBlJJxKK>g_O!AMF{}1kCOC9-|G<9uI zeC|atu&>3&OA$fCHKPmjwdNt1EdJ%I2Wpse9eo^SuL{xW$lJ`c`; zwYplLy_^UF4i(FWT5d(EAZ1`Z4>d}qWKD6eeC<%|7IO2A8`ja|Qrz!Z77#skQ7L!d zuEyzDOD^6ihItgWpE$T~t*=uT>Op9lvdfK6GHRnI)?d`eP{c2I$R6yC z(4niy>tu`PsM>+7J6oR=D&zGpjX~cXMMcW$Npuaxnto&Jj2My3FawtSh|wgQlRpID zlJRh5^DZJG*t#=Ps17#U=&pL@ORgX-6MC>^gL|Y=>?SY+8=Sj1^V}qg@E+Du4K%;L z0}MM~Dg@Mduf~r@3bR51P5Haf*_P^kNh;0*P#S}Ih1c;3*#)XFo&{m!VaW-t3VYgwVfhYcx z0r_{FALfLdnqYS+g+6g3EGVgY>t2PUpm&E#wSnVMVaV}%{k0nPmM^7Bme5WIw9$bI zy-l_1*|EgvowD7c$Cw(#*ATy5vAqOmQYc$2~x=_i$F9%=U-qcB*$uD zgpxSEMxG#eJj})=3BwB-OW&d?L4GfU9fxK*M6P$v6nMui7GHD5Y(`g%p7{2C9PA3< z3J>f2ZFIr_^*;9X0T`e7^}o0_DPZs|(SB8q$X}J?|HU2Xe^ZV89nXx4`y~s)h%{2t z+8U5wN+L>+jVR1H<9<`f$S918@-~`(-lwENCdFRFx=9zOcE1DqB0sDF-49$4B4NhW z^V`kz&)@xlO-6HFo!>Sj2F2}3ohP*<3s12E71f3{TPIQK8-O_N zw2@S<(kDgTY0(XmlI<~ZxNVPU0yhcgWf2$?%PuH87uGm-3uh?<137tb6Lx^P7TUzt z9{FRv3XECEW)Si12-euuddiz0fvTKW$X{xWtS1wsrA913>*+J=crx8zNH+a)=Q*MD z^h({u?O=uq#)43k`0&^=!{v4MKTY7u8+5Ndp+%$nKKh@`A}W~mrLwJtBl1-Yt{M-xN>Gp{<~FNSk`S>pt7t+gGJY!pG$u<{@zEN{ zYERXVty=ROZuSgf=$*rs@|JYJ(!cEKDZZPs3xH4_JzT72aX*i+Z+AJFJ=HA#>;$n# z-NqI-=YcYQg?G+{4>g!# zv;3q6mcP1@?5ApbA?~j{kA-h$fTWUSnMoVcq6z-?lmmhU5x0k z0YN{!FMhb-J^l0L*Pzi|$$s3ae~Cvzsq|Z^X-92m^SNZr z3hLeHU1gUlrQ4FtwBx1edYyO2uB%}vz4*PR$?~6BDz0QL`eeL{#d5Z{CeiiHw$Dzo zjPac4TT?$824+l9;C+rv1-FJ>Vw-K(aP*EtL|F&(`Rb13?I+{dpY)S=7FQF$ljg>E zb|{M%;}7?vkW!~yf7t%XzFdNK3-9c;H@kldQ~{^UCKJpVjEgyoVa~lkoVYIrr}Nof zJ`qn5t)oM#qr_~Yi!aMwOfo&78M@ud-mt@eJXG(=FU!I!Z3dA7O+1O}8#XPtTvQfI z7&G4P?#UCB(H<;~u8~%*HQ2-EV_@{J08#o}uDWQx*K+2{WUR%_tiGC=sNr zeo<-jAh)RT%x@)(JhbWM$fG_s z8+XM&XoNX&Ij!+@ku+M>?s3gL*}6}N_@NX2y<`*Iu&UT7VavKl_H_L!ft<%C=p;Z%kq)5M=CCu6|N-|n3sX$)Bn2j{#6);Kmbe7HM_$wJrHwUc~!^|U(bnUp~ zQ=gwdyyp28{$!`k8cHCzRzx^WW!}?XKWEu-{(1fR@rNDg^@cErLYmW9DAYi-nvW7v z3ZWS{6KQXBY>^dw8GRBk6UzD9M+l*YsuimrVZ(Be%Ucqm1>L(H+2A+wu#Ln$7N<8` z5bcXLRL)42s5fxU*scq>p7ITVr%fe>USdoynLy8CP|ZQCZ@KWGhWbSms^jJtQg^T& z^-FZXHPuVDfRm!r!&oJ&TmL9WJ83RLda*4jbGprWb8q{8Lzy&P)$v#>vw89**em?J zJp$~5NJzE$a-*;)GdXH?Xm?&EYg%jkzDNJK#F(ogSH==^81nPE`P68kO9ed}8It&& z-(3it62X*=+{qHB4e>x$a-Oy1M5IMBQP2A?&w;?WWZdedOe15Ig7*0lAs@D%E5<9J zM;rS7>XFCK(HQ1nhmWd+GSXJXj0qFw(Q&M(1FKH$mn+}@G@7OwFPa^&xqxR<21cJz z?k<~|IZvc6$%svmC}!utZOW3sO5H8$Bq5-z$nP=(oAlpD^k%DY$?NYs(*OE(z}sZi z=JjXd@o@z48L!xH$r2JIFQ5)=oxxH%Jw2g;;@N)4Mhd{H?e4DVc88%9SzJODNTKS` zNA!!OA=-->te$N7nkHhq+tPuWHmqvNP6dd0bgcpw@A+a1Qg8N(RZ}53Wza#OxbhIr z>|UAu;el~e_S7^z;9@>Sk4Y&Q6${&pMG7CvIz|FB;CoX3`Q&$wqzjm(vFq zrF35XbtZ2UPh)Ub;fen8Ggb&XtyK%t!lOGBi7271kCXBMqarACaSbkbqJkg?2XtV1 z@l{0$3VACjA{%ru-Mc3>7(FTTkVa+6mdh-!=c}UK2CoS2*Ul`T4Xj^Bm5EayL=kR` zf?kEv=oq*4Fz~2UC@U3vBoHc@$pgTdinp1#-7y_hTZ4#TPoMOx>bXuu>CB_FLKB$m z+8_JLNlhft1m!QtzLk44q9^&ECGMp!(7xq+W-o2wEjLe^MMkif2Sb_H_+-h}J83Om9uG|^aO*Lw=f_%%Wdt*|hDRm{W5>>}(w8$!>W~Pn5%LYaM z76~;*>}qRDX2r=vRAx{dWhKg0VnTe7I$nHCo)BN#~X=BU35}eUJH}S4K$L(vn%^l9;hA=l4} zN^rTtaZ=q*)fQ=)bnC{In(yT~OPLF3IYu-l3K}>#WzFhq>jgzkmc3s#uS5gM=#1JX zT70nYt^<&>OiS@>3yN(MAk!ZVmD7sCz0M^|=4CD=DE^P45^`L-Y}j1>b8F#2W7cFsi@G>)mZuK_WCIHnHUc8?-4RKt zM*AyMp9qYjIWR=9jF!Jgz==b-C^mRet}i_O)}~0o0GtbWF!pdyOAgAOfw#@=SIs*| zy(niGvkWoCyKb}Yd9T0=&wCN(nu7N}lJNPcyif{v^NA}1Jw!;;Uu}XS1t&ug4nUWC zgw5eXCuq|JVh=<_e;nj-hhg`?d4#f&7ZRug-rbWG*>XXI{DMOt($ilYu0M&f-ID1+TWHr5!h@pTu<%CFzj1_+kOfZjkCm{_v?k_Cm z7gXnhypEqQVQMjvfQaM~v$J+g@LqtsL3-C2pd)BRRXjue0MbfcI|ivHC+=YnS?;$V z5ljs|^@R708nUGgY=gM@r(gS8@NcnC7#g&l#!+iDh_dP(N(XeWAPtbBJfOodki=9( zQWApHZnv#HTzlY7Axe(L!}IHtyT@zHs|(%1sg6=uq+Wh0!4XjXxGk~@A)sX)AcreA z`LPi!!6up{WDJ2Q*d0_FS$nmVjr;_9R{VFyX^cY2Z>7d%w}O}GokRpfd^cG9Gg{k2 z)v$=QUKAg(r$c`0PTlvqSi@=x^tOWNEvX+ZQvIFil!ctj%#AL@_cv(MbQn|d;Ajcw z=V`{MAMd0bQp-z2@0m|mWGA-SE-D@xN{~Y3#P{fbwv6}=GLRx0%tC|9H}F#l+jvL7 z9Tr`*;hE^(S=u?=W;QrpRmiV-C)>Q-K$je@RyS|-{^FOGmpuwz{6|1U@I??}df za0C;wU(+^~ySpIy|Ktd4d8DMFECu)e;s{u(X%VnYe_heU>D})@z9NiW`0lSbfw?VN;qCMV z^3SOft}6+Bw?VVlI!tLk+G2Dw9iH=)qh&@HU&47Op@#L>o{j;DnMyo564cVf{^Os2 zL--+-yA~CxV$`mb*S_3{gb421eLBjy<2n`}rdO%)8t8awbtsc|;%rB6$AwXX&McQvJhxp|2xCx-Z-vU>`{PxU*R5pB}-hjnKwgNqA4ag18) zd&TrnE6D2?M}UeXZa?_N5r8R*J9Q6gIQE91roqdRKZKLx`?m{Z02`--ZLs2dUr;W%!~y{tDnJA?6xii z`C`#lpMkKSpNY*BHtKwOv>y*Tn`R1I zNi(V^itK1T;jh%6`&-Usc3E0!jtLmd_tErEDq&LO*fFfMw_2@|q!`9^_+M74W!RrbAK#0n5j{#v7_OiyjpZ&AlfsR(P^RU%HB_rf z(c188!A5?g3SuYIUHYv^$g{lckYsRNnd)|#OG#OTf|E7=JLc5nC81Q8wu*RUN!fSUhO$TmLQE+o=qKCa zvEEa=kG+-#;*~dA9hh}Irs8(p7JGF#EFqIU9sEjKg!%OWR^95w&5ymX^ua_e7NDA= zY<##yb{g1cDL|WO?XiRSo)xoZv=V0R@>JjW1AIjHP~!h&WfG**8gIaqeqB!iM5WJ65_yJ;uoxJv^1s!?+L97oa)faV2J)wn=!m>GVET?}&#>>g;@z=PJs?rw&LDaLl*v!cp+JkXr@(!#E z(1G?2`R%LY`%|1(96Y4V{hJ5w2-GTu0^E9;{2K$SQ)Ie$+y#n3h`2gAQ3!O6dVOL- z5rjCgBbtg3R)M>}=9wOj#Gr#82eGmPo(i$@!V`48EFk&uzmRiz@}H5HUm553bx@-H zTjnXcSpP%0KUnT>X0Sd^>6KyF1zEwEG>Yx&TF|--v6!54I2FGbalNcv59={T^%`y< zUkV0wVCQi;7?=^aMi}(A337mJ?aAzk8PGULA98e*FnDGG&|Q^`-a2Jb#F=JMkDfy{ zo>eJ;>q-}0kIRd6hIElAmhoi8cVs4`X|{YVL9+sBRN|3O@tn-rAvR3mr@K_SW6It9 z&G?FTV}eArOK(eebf)`D?vd973$6}w=VObHS}6UUGey;?McrX~=mDK^Lk)*r=#;9y zj^zn9^m_Eujq{oRv+KFjY5P}14&MhFa#kr8uw`Dz+k&*-{OLI` zRY3?y>4m|{#9~O>1JpnPT+Hdhd4Z4iB&0cCzSyDLu|~`{k>mI z_R(?6%1auMPewOtuGuLk0dLJbtoBcZl4}Ta8lRZmSFU$Lfh28;>)li+`TlAec2Y3eujNsl6w8oP1DV^x)p)J81?d)%6tck` zWt#L<0-2FEI-Jc_liNtqg7oBL0mb<^K+cu2r-)2O+Srl^GZ`)}`q{OryCjT)kWra7 z)4Ev>G8Z;0lc5E3I_oqebDOYgL0z6iG+(8!JEhcde5$nOS`267W9v-bAB`-8?lp64 zkckGv?Tuw6%iZ5ugJd zBZ`UAN9_llh4-PYYZf_P`*G7*(~o*}XB?O24&{$#`rr7RTrkE>y&U|cUP2H=J#Cw?pacVW$B9hXou8H>gD{|Th?=^JSO(A8a?uBOM71t6!gT~m;~dt; zPK>IH2?pKiV;j?Ld@VaNEo-@GoJ|QX7do|G{Z#Ir-#DR+m;DULQ)~%S!nj|$c z3E(h$Ao59jamTlr;asqsS!Ka!%Xc1TIRVkMo3nn`LaK zcHjdynl)xhf~R8*l&I|a!9Gh1@t*M!`243cplyM)7Jc63&tR_HDf7V{@Et|Jq)zCX zHqWeWah-m$P3^cqsKv^7Iib`^Z`^|I46k#|kIhE;Wo>|}=<#{_`V3DxMnHK$nn{^< ze%7MvVKUhsV-%oF%gns}XrAJZpTt4v-U?WVdKm;898&TK!eurXjx$iz!2&&{-D6d4 zUIZ?MsnVb|(tvgV9ZrqOTE8`X*AifbS#8o9z3U0U!E9!*HffDBz!^Z;%E^FlUj(^W zpmS}D(78IAv!&Y0hPsVvPyObMP8FN3J<8% zzm9R>I~{5X>Ro(ScUPt)XFD#J>*Bu=wC}s!i-t_jfT3b2mNS8g?D9^;IpEzL} z&cUDqR6L}wNt_DMT8a;oipWJIk~yCJ9p9k2(6{G1b95K}twA74%CVI*cs!1L%;R@< zZhi3UXqOmP4h`mYgIU8M{?FpfDC8?rIVFxcsjV+>P`-*WA&ei-DX>RI;&h0o52WER z@1v*jF2%-=q5>5J+BtsaIw1-v81^2QO}A^>j2D>^ZH);E_qjC+Rv>AJ{QSQDUgnlg z2b_1x?3+TnM`vUewB{}Fc6FMON}J|)_?Ncno&0w+9>lNDf{s8++?nIM3<3qWxKb)C zx=U6&)-0MY9w0JBN;A5Fic!2#Lo6Wf@RrMlgJhAbFx@u#7kxT+qSzAaefGT7+3bF1 z)U7T4T_I$-HP}9^jo{u|eO>Suye#mSB?a12+_gmPVMS@sM=)<|$RKnKZKXnqWGgoF z42X1X9u5a6?I&<&!Wp6y!nr%CAj6kKJis{PTJ3rLQ2dhBJn+aBT#3|cz+9Cn0E5f& z^qgE6;dx|qv8OqOjS#URdja~W(Rp#puWl`y80{U$!#bfrYmu$+{=aGNS;P*13m0D=%fPK4E2XMXFeA_op^*#bz0 zbx1}-#Nms9ldiva{)6ZW_fUw8!PY;Z7zcXe%8|Tw^7b z>?@aJ?7R&HxUGDLf�^9Ew!K(j87!gFtE+SC|A#I#UVt_@yA-l7(**V4qD^4ptMm z6GaRn1&jJ%az?1}^NJ_fR#63mp@PL8qZJ-fl9^Ug znbf4ZM++F6Mxu@f{WX?}8F6pD4Os@KQz4oDFW^U%p3?6mFk zDmt(c%0mC$KZ4KBC&x-%E}>7`DOI4mW9c3+GCu{r?Z@>w3uOD9KOicqZo zS^VviU_O4M>Cy3%4g(?ei7I31`xGlLPc@Sqd3htVfS2Z2?TUHGkZ9SHB{xmw<4rXD z(J4t%6TShX7IZ|EtGaa}O3f)oTfxYIMY!Y=To^4Wsi{qvhJ;rYc3VMgO>ueUJ)uK4 zF{72#z>h>DsiqF|ARu(&C^TS)e$tvm2{yZ?rk%J;>Imisiv{* zH^>ed>z=V+vS^)jMr(b#9-X7wl9Fl^D0z#2^zxB=>@Ehq#;m-CDDb+QYjIuJL2`LP zyvCfAQT237NV)k&Ad0d~5RMeaXJf~h76GFwo8acxP5)T~a*dxV$WIbH{7ICE%AHZk z{R-yc)el}7M=$I2f)YHTPVW;}A+y9Qnbu-u?woT@s-HYJ0{g4A(aK>Zx7+&Ao?V0_YJihBm1}_5v*=hW(-#R(Z z9yQEw`}{vYVEqidGX+pESkX{JcMO@Yh5|z4L+e9Dkhf>`NoU|Xz!9jRtF_~26=CW2 z!$V70yj#GzZ+Vf`Os-#vvy)Gmnf@1D?;ITKyKM`{wr$&5v2EM7R-6^vwr$(CZQIF; zlP|w>&OLkI`u44Q8})W||IyEUFvlEYOb<3c{M``82ToW%$z1T6i9=9C?pvh~>46y& zN0kA3mQD&G|E|Ck(0$IdZlZk^*cuBr9Ra7WLIny{ng!Kbr+a^gu~FqrK}Kec+AIaz z8IiUCfE%zGJ0i6Q1XP9NKmITYn{o-^yY(ssOY=qWIh88lZe@{&P@iXVre*A zX!B41h{epD>O|wDl_Vvo^m}Gm_qYRcl)-2eH&1B+mbULoAea11;*I7p!LiJ8noQ;@ zjRWmC>Z^pKN(F8!b;w*t7nWBMse9;&2*X8vgy^rIcx6E^p|}a zt}v1hhKeaK5EX2md!tDQvdfGcifvA)MYgjk)ha99>j^AoRcUX4^JPaMx=V8lutFRr zn5xCmPFG_RqX`kgg>eRJqlI4l{kOCR#Smi5TZ<21KNLqurcYl`w-@boMrz^~9DsWa zcGjq#og?={qmenv4rl@0v2RY@P!_nhhbn`2&fVZ|E*KcQAmh#Uwhch_|Hvjma4E2; z*EG+*^N%Djuy~akX;kiXWR05^Kt|29Kb*hE1jjgkz%_8;p)&?(kZm1HzrvOKb9-F_ zJ@^;t>D9pS{DQV1A;+GHmXsWX_SrE;FRiFd_gU|DNK)aoTU9zX8Ox1-Kh^Hht?0L5 zn$O@Mh`BSIyMO}R6>d{M6ezRFD_9RInRF>(#nI1dPF?aip>}1?mB|Z&4M$JqTX60avDBVS~k>R>TVgmLIqYT%P=n!T6FdK9gp z>ecKN^Ca$$Z-$nS0|>5EA+Okin5HH27$aXXP<+mioIfi(XxA31mwy=|>JI+&mX*2`JMt$DM+wEn)~u`(s)n`G)06 zk&2}$RN>D)HzAPW-(z@U&?F`UU%ls~K=b{aqr!G|2zY+e%d6>4A3d+tuOFxx{OaO{ zV#GZc(qRvySA4a2*$_X+f#;kSZ2&Y9#zF8&7+Cg(nc5t}#3Noj@PSLz5H~aWNKLm8 zVn1$1HA%eh!W31+6Rgq&`TK@J69aT^fUg zYywm7K^-4QnPN~6*eqe$okP*ZAX^t$UkC@qGXiN;pA>lf5jg2K$nV-5^KD5o3O_Lp zL%nC2wnR6tZ!}^r$rq&fzgDM;x9|LNlUwfj#r8-7dtR_+-r#umi2MGxG4IbjzTBdjBSLAVdD2jMKPKO4rG(OGE&BQM(>3qXP?=#Nzf0z+TMyVgjup>T z9bf0yUD4yk^Znj0@DhcJdC3M)e1sc+v z`#i4+gx_BhA0eORR@PH= zd^*M>cdx?uc*cYjvXm$1xhTi>cQpGAuPwMs(c!yY1co?fdgY$08LN_0a2i1%z^%nse7)b{^x=LH!WgoJVjK?|>GlU8c%`Do1OecMJ23{5aw9DpK~4}p8mbEN2J%)0 zQk;ZA)IhNZp3hN}dp75>Vw(zVR#YspLE6l%>~e!W;e zBV36f8b+WsCoO8OIW2c<$>co{w_%FxKtmL2a-NedooCfswU=c~m;SUnMnrVPWz;wK z46a_TfC=#R#=<_ zRjf#Qox9#RYGl9`Z0efog)m5}SuANfn zcLI}r)LP3%YCX0(oERw~JvV4e%rL0^hK8y)D!yACnc_CYaF*y%wRYH^TJ8kR?xI6n zU1y~#8KBejd3>9i0^{V>&gWA!xBJ92;9S2uc=)Z8z-qJY*W&7i%X{odEB;Bvet9$) z|Cgu=HymyNmT)>dFHtr;3z{k2S;x&XM>%4S9p3FUpg1e;2-&JY35eLhZr&+!8xCTx zh&i(xK+0@-Pr__w@6)h&JKro3NkG}3OkDU=m{~U4jT{!a6*g5hGk>xXNqJPr73X&) z6DOBQCHW_6_6%Fwj&pQIpd+|GB2(S(6yq9Z_j%T|>-3~fBUd02y1iemr!2Fl^Ki-Sftj^bAmW4X;)+p#Z%@UsY#TGHDkrqGK=}|)*Owkm6&HWDT+kM5U-=L| z4@Y-gr*aq-+IIeh4F6MI^r)1HcKewu;0OQ!hppEkTAe&-YD8>+&%(6`~ z%zz0BSjjLIZ6l3B8V7OmIb20kd@{U>w7J31DuPfc0u3k1(SC4ObdRtDX2f0_hFm!l z968d@WEJiqxp%cq$i~&(b7)o`$bhtiF%ht6< z@*yjnHq}~FMp@7}3n8jJ|Ke!bozqIQVq}R0)RQ_SLp_aNT?Hjg0JK2gA(1urrZsKJ zC+Z)iaL12pZYt(Xs8r#h~s9M#Eo5Be(T>Tq8vR-;sSJ)Ta<7 zX47vTxNiiTv#SJ~ zv*)y3G!$RF2i@lerblI-YV_z-y+`d;wT}(EV<@Q+o@%-th3YRz{}k!>aTu{^ zxP`jLHB!9{J!zW{ymd_`QjP;*4f)5x^^k+8^_s3|l*U%$J*mi()I|^t!=Yzq zm$v34Uj;2R4x(Uk&63l%ORGWbIv%HS6!J5XjZY-qVJ0vQB%UMkFf#2$QRJ4SwteXO z2m~<~HFM;e0RJmgPs=f4rjXO1k^!UoccpkAN0TU`7d!yTU!m7F~_2hV`Bo} zJf*9`eF?u|Wh`-(02?pJf*^Hpe=SVb}P_&1?FQ}aDXM!hP5>8h#S+iXXuxF4qqZIPP zoor~nm=mE(xkhAIo1G%Z15`qDllad08)ibO?MVsLwiFfQ0M3=T?#pZ9!>i#?vi7y7 z+Gt$?m+Goc5W|ONh-ns=4|B7{jAfme`+qu8U_<>D=)-u|85WhKOu^f27fEcugH^`SkiW6CKE@sg((dz1_ zqvgKRZS$`axPq#O^$s!A^$!tjK=b!M@Vh1qk8s^O!Bh5Mdhp3Ujj4YCCFjsO)@LxD zQ`*<_@<+;VAQ%nxu-sjOr~2Bf4Y-Tzvt|ar5o65^JDIPK7>K}8i?#dokwVEHFwv!> zencnZ=BZnSrvDDq8sMz-0B_P7%72>d@wLlpD9(BUGUe)*j`)riJ+6o_xHicB%`_PR zGlv)5Kku_XDoCkgaht>2oHj`H2gVOMO!`b ziPB->7r_%90P<_lCG?@{1MZ-?x7KjiWvKT6gtr#p_NrCr&7~xm$) z!bOs2BBc9U8Zc|MtD_wgMP8^#C}r@U0KDbrZUI=r5;SW@_%oTD%zw9Xe*DU;^}R8K zQduW5qqvW4X!Pg$CF4TBvE5=IHCam{^brW1L~Ss`m4#5;{fN3ICU zJ55r3<}W%z;~Cg|Zh+fAduvs24_En?&hdn*tN(%qY(Nyg#N)IC+TN>*k4^;NySI+G z=ubcEPm3D?1UyJc=3NlR5DAQ40)QsLB3a#JzVDO3B}URzGN_Yl4)vGl8dr#hbX6c- zMa1|?9Q^g)aekpBHx_%#3kPz4(HOL8vfFD4pi;$Mbvw@eV*m~(iN+0XMWJv5c4k0< zwXQjOWN>9wk#L^sfpb??QLFW8PM^EBd|nxm{BD-l?@WYgn(dS63on2ETZ)Is-=-S$ z_)PAo>|DWav)_RQ??L|6d1wB^L{4lmCqX1u3~7;ma4)Xn#EwuRO|5 ze5tuGoi2+9L3(N~dZ-z+Dh=IMVF&dWEppUw0AKHq$6)mj%{{~VSC&sq+S$m-#;-q6 z%M0TA)58$QHsMfcXf!lT#@B6Hmry$=Hhl~l7q_0eu5Fen7kB{>=mux|{x5e0)ZnW3Bj&YsckZM$!rSE}O@_>oeaI#GC%Msvb_sV=+xs zE6#A7j($RL*)v&k{FSFS7_JM1G;nv&d)U8*zLFIF(?+1*QyQrCS8V3h0R4SX4+}%(MF#^q!FXX z?$g|3#Hfqzc$NLzF#eOb1)$sU$Ndar*UvCg{fC+SzlO;FByT3h|7MFE6({v0ct;7E z5qDpr3Wa)E9>f?FHjJc>g1n0}52aK73mnOzzHYpJF6UT59SPk+_M>^vn%|8G(wW?B zb2{!g+3xo3>;ziv4-W$|l^J9#DR@Oei&vL63?saNByH0{A5W-ua&a8(mBk!}(;j(f zJ6LJM4dr;lHxj>;5PN%vshju6>f^B2@+VYEh~|K9hs~pNA1IctK4ycQfRsIt#%7HpitrD*w^0DOL^KxD6f4b~x@)pEdq=Su$30 z!?O!9`GOerlG1+Y{re7C&JM$TndiO-R!$jR#P*AMa{V=nrN9boTN~s59;Ng2eM1BFKH1hBwbC~_F zxSY9rY(>rkK+#tR0O>$AryZQAV2GpoYv=B2UJ5W_CBIVAXG>QaTs~JFp0%Hx#j|2t@t-c{jY-Txi zF7n{4B@!8tBi}xjYE48&_aW@a+jA$gMPnUcz$&V&i@r zLpL4%4D_6y@NLSE!oipHrUz~>15x=P2%#Hwcqj0#MezQL;fpR1ec(kFBP?VLkfZ~RG&B-a!-$VHPSa?cjpeDY2W}R?P~^N{Z6l9 z^d+SKQ;g}YF7iuDy$h%Bdzakc!w%vbz0Z}KIDs!}_lL?pt9@7k#7TjwYLck{nC2rO z+80Zeas=fb*iS{1P!Y&R6f-VqQcC5ntd=jLgQmb*t}1?h*q|$pbvQ=>lY?xK6H~h6 zR;^g2AS1X!6O&wP30u*`gc*0^BKT0x-Hj;9rluA}9#eGC);V-lhve@U zckt@WwBoc#^o@*p5skOkOzPJudO`a30jes4yakRvhe_%0=i zWZ_ud=WnQm%wSAmBX628w+n+U>Cr}-@`uf%J^s&kk(mojZONFsZmI)0Q>0Ai`&mmw z9@Y$BMT0%ydVv1Xn27fD(5T=G|jZ9{y_&_cwk8`8R zT}hBkS}#kXOi!h*^FuX}vK7o)T#b$k2P6*AE3Kkv2zlmrg&)cfKj=MmTHB06pJ5bA{g+2XM z%M~|I){Vq3;IW$WCqTyZOc4x8SsIK?;v{U=BZ|e2jOs3g5>-`_)*Q>U2;*bbU^=*L zJRwb$<3Nd`8v;Z+M1us2S2mC*oZWVi_tz(rNN9SqWXnDwySBJ>fPAjLYhhNy+Fw}U zD!Xi9YFHn*qlrRhIS9iHLc^C^-J=AE(OO1>a&{BRPfR0=!&0S?azq$AK9Qb)%y5K6 z?6%@wvm`0|Y%DUj%v}Wa_pj=aZ_dx&kc62%vl3U(bHbxyT?_y95o7Z>P@K(oc~ROv zZ(+zr2+XK#G**ubBU5w<1y?=B$#gw~GP%8n-STxe-=rP*meBHgdSvI9tr$kX7uUrr z`GrIW$r-9aDdbrTmZ>$RbUYl{r`Vo=_ zx9@sSv$DA8ceZ+sjJ58apwv5FqG9ua+PO4&tkj&2nv6$XsS_3t9-m{=s560arAQuB z4;n{3Xzbbe8Il63=+4Arv#nBFQ|015eeIJD&1Lm)DRp8t#P6!G-MzUE^(n`3g7$}M zq{H=#iYilNtct_p6|6B`rS%HkY(L{T8TzW-_%HvLI91xMWT$P=02dr$&sb#&H&gbQ z7NnD}qGqHe`szkth#zt;G@Ew5fRQ^T+FQF96RV9T*mii!+@9unX&zezEtu>9%oSEv zm|4M@w6iQV!V`EuQ z5z%ZfXQpDPZn>|rIvk0@1PA+jzy1+F<7ciaBHw?Fyox{B)jCQ42 zZXAWKnB{Uh5-*|&{$-iK<^2Zf3wRUZJG6+Vr4@@ygiCg%Tb8}O2`~A1)d~T@9;kSx z;!!4zclC1xlbj3th=7bUAG9BAQ=OHhFfLW=Mx_@Zn^SXVkrq;h+eyr0rihMqLt?P3 zg@3UubJp<{SFq2)SglL3mt?^3kW55jSjRDGb}Rj6_G4gKi*4B!0kt8@v;@ypg`%9; zy8}yLyfEbu`TDuJDvaX6#9L4w(RZl#?;((a)8$(*&vUV0$|Qugq9gN)7j=V8y=3^gx^P4osQd4wVYj7KwkY?SHs|vwWL*7sCxbuGudR<64M)?{YIE(ttoIGZ%aTC zTSzB;n%ziIORx_M?egJSCB31y;SIjGV>#d08s0uTd#a3ah8gCB47t=Va$VKx@p|@EQX|+7n)2(swd$7E>Z<&75aN0sdrEb zX$5tbNyET7+wkhj)h6Mq-k&e^^~yGWTa9j)c<2RbvYd3h8I$ADD6G)dfo`vU)A3B! zg?S=YS~IiZzWL~Y4Rt5SyTZ*8;z&X2lq~6E)8w`eocd4ainIwWi?9nIAsH8jghR-_ zurlEHRIN$&!-Fy!HbVfg3SsE+myu1_+bQemwv`KqOsfhKIS1}3|9jfR2Y5G1S2#;& zyo`})Vg?tqzV>@ifzr8~mOO47w8!88DBfS=j977FTn}JSn+g8=4{VRE-_(LUlcAdgo3x zBK4Fr;>N+v8W<>YBi24~vmrreh)x?lD4%FpV@4rXq#&bep=&H#EZ~(W-Q-3nEgGpd z#H`bl&hU7JD8zW)q2b9-6!_piJG8`uZE!l)5WC?jLmIB@0=L+1wUH&OPQN#!> zn}?v>_x^hIRNHfV0(lMM{I-rfhT;jYmNg9+b8n0q?mWwFAi<@{eTOk?$r75Ow0Bzy?YL3Pl!!2KE^`)A|S zYuO=JCdsLccW}vH2~-K+l_6&@ zJ`G%^{x2fB4Ea-EN#<>;HsEA5!nV2BDB!~Y$emd-U_%zbO;Yyu+;zkzsp=&)XQ5N$ zMZbP7{VIm`GeLR>R=}^f38T8>`-W8+=}mML0$Qwm5hwS>I6p{Wv$=t1I__c<%)_@M zmN{T9mt`Yl?>ENr+Vwk&KWWo{%TDJ{#y7>By(S^~=SGg>7KT)pdH1kx5h zG<*Hk;VpB%qVZF!pFkJXN;ej`?<*$$j5UYwrSeAaxWIp*h;=$}>9@M}~4T--9MZ2{*2l_IbfuLpi0sb=4VPAj32u1-eP6OVCT1$>>a^(YKbEv^j?RinW z5E7ZYBkJi6aCxEA3b?U|GtATz%fipK1%&GKJAIEtYgj;Rh-~x~77jlyaTrD{7V~Wo z$feL3Quzy>4Wx37Mxh&?HTC>Jfml+a9&?(H_c;BPP?{Qocyavl_8PIH`?zEo^?_eM z{1wXhXf!-9u^ODyiq*t~zJV1~x3p9`!s-Q&T_M&4)>-{aaO?CB)20NWErk#xlg~KK z2Qxg@09{LM0L)KMqq3WmO%S$I{c57+F-`SBl{y1eR6Kj-R&DA`?xNIRyeGHq%X=oF zd#n1e7oyH#cS(DDrmdSWnZLbW+|lKK;ot{nLA}T$mUzTq4^(60kAQ-IioDy=Bo_{u z^}t?|6Ufr(Ta4~0Ns+Phh`4%XnUdjY7?&pauZPC2g9zE&F;u%4ypb%l(ymSvqWwt) znKt*Wt>ApY7%r>@nGqjudqsIW^4=wB?jZd}>o|;&MC(k7u_Y&Fqd5a)skmd39&SG} zWT+$AGZm9y}QVva9j-9c{h9j@Gi`E1jqD)`BBwN)ht0lf7M)nohq( zc)`RIUyqq9qE~W<@W^sjT)hHXo~hOLo!CRaM>*0?3}>2-lVnwPWMf??ON5ds#vp(B zM9uSJ;b>yzXiS~ptIL@Q|0{E2^p2p$L7`4>>k!D6FAOZYgf~J6nxk`5po0gzsqLTZ zGp+a=>PrJWqTl1!>KFdMaI1gnZka`W=~O>%a$-Lw)s+7sFBY=1u{UtEaI&-guXq1A zhWvMi9HpwGglvZ5!v-m#zJfsHk0dOzh|pcAX#-k?!CwG4C$Cwjnrw`W_TblneFXl3 z?lG6)+L*PM-&M-&-+C2h+rQ6YR?+8P1l)F6GFx(RKd8x)?#aQ*Q5r>lVq?UvLjV$vIRc*WXt6g@eW zTk#GSui^cvVQtr5~2;^9;$yKP1=JuW11?|Wcl&r&0L}eMXVv(L^b>X3a+Fa zbz_ctI$yUO6H2qBm>D`juK1)F1;_FAvi~pu6f_Q~jcYhKfssnYOvNvr?79DOxINAZ zMiP}u`ZzOP*U)yG3F;WkX-!kWz7Mj&kZT2%1nCUk&#M$uT^T`Ki*VPh2jqO`hit;-r#O)Mz$ZSgAB>S~rw!i%A>Z^DXTZ$Usvu z*A(;vGr`llQbJCQBPbY9d&wT`6Ki_EFxW}jmI&29FYix&@XVTl|Couk;;ak8+Elv6 zI5ryXDlu#Y18b(3FN76Vc77pYU*1G%>}Optm96=U{}_pFU~TmtCJiiGY1da=93tqf zywQ?Vr@Z|!v1EO92-ONCLC6ZVB$is#ALyR%+cjG7@ zixv>M3({$)dv5wCNQdCFX@GcPa0#k7UVbyNd)!20FX-;WS+tL_Jz9|MP%R zfNt`TqLXhBagAv!ToFE^oC)7x$u(Z#sv0IiLesEuuuMaEa=RcPTgB!?uFi_l1|jv~ zi+BX+D}LkeF5>N?@S;X_iVwB@A*Tq3ULBEB0Q$;G-z{l)Azuu+v8O1uIbb-5gt0A% znl|UH?Tm7rA2t+jb}&w^;@fTQ6kv6ZH7K=>T#l+F4JnxCP^K@(7RdJo*@x*nAF-jj zz>`_Z<#ZG%(N^~aMRgECVG$4VB7?fWYY|Dl%c6i5C;!w6wHMQQ1mtp^pA;oY(eEhD zb*Q7;bb;Te2Z@tvr=J78M@G^o@U04o>l9?V&i1}UP9gB*4qTVoxt22d?ex4;7>PJz zN2nRHE_MU3NS*}XD*yY9@)gzJG3f=R+9uGyC#a?BzpB1nrVESUv!S0Nj+7DZq z%kHa{{kA24pgRXK8h1lj>XHLK4d9hW$_ISQ)@oy`Z)F4zCC?TLBOP0{|Mreur`Stm zV-lD3BH@tdaCJg)NT~|=o}FuWYZ8AmIiCL8gYPfcpK@_-A)}rqyT)RWHL34O_ktom zC!}kW_*RrD&z+6XZ;#x=j(5prV-`@Qeyca%|9io`K-^4n_*rm+VE_PV{=xLB z<&Zq5=52nMW~Z^Eiv;|D2gclh32?#NWw>98OezMmy~<5ZTJ3GPZkJ^~W{t|OOJ^Rq zg}yd6SyO)?ph-TS5Ez-vWndLMGg&pUiM}c}oJz02dT(ZP-g*YmQnsT_8kS16SxMG} zHxJOhe{Zw!`kmL=O94}o0x86IApfheo_(w`(A-yAq|y7GgxYj@hQ+hTN59E+Occe; zO?IqPw<9tWXB8KyWQTQjCK^R_a}<;?(itRI>3It-Yib&<+I1+7oPy#)jx16X3E6c+ ze<+`gC(v=?z2{=#WTBUzdgB#izLcC4sI2h&!nHCti!*a{!KPcxeS9^~3G?T)SI&Ag z9u=8tBZ#Zrb&z~Qb$mBeKr%_m)nXuj+!j@Bfv@Ox>|LZ?$-Z|W#b={b01~~?yL3|- z!5;(fQiWK*zD7H_^>I}3qlcO#dde6W7v6MS(~Oj0WH&jmKuHJ51eFO?8N6aQZGy>+%B_FK>ufsX^i~}7lb5FHb%oR+?@CvT)P9GcE=^38N zkyEa%>V6fcbl&lhD@rjYn5k(#vO$^6WEWDUHSndD@$=5dV(>%lm174sS1Y&@W?RLq z2A5=+pqvE*+-X5Fk8*mAopaCJPx#NptGOWj7&)LN+-sO zvJ^k5lHV|FPGHdQ+v;VHnf~{e-u{r6(cTd>-nmrXAv2ogsnXij#B!FJk-my+PB6IVM&kddzN*2TCboQSTCbJEiZEU4#$A4YU-1xBkY&$e)v%coK;N* zzbp5k-Vt-p0RuCGgRXJ7c;zU)SRSQWiPWYk&$>c7 zf!CwehSYYU)jqDAZ%^0_4OeMd`cd=c%{mG6q$dmW*vc)bvV)MtO#n_B4GOY>7VK!( zhk%>gw7w?nRucmIktI$_(bancpryKcckoTX59cl{?tQ;mFT39hR@gevmuQMPT{8m;N-oLa`!g#EmHJc-Y+$qV9EN zREo6@5LQb#HjVc}0=G~8asM=e@r>``ma|9Qd)4J0@Me5kzoU@_o%M8@^=z00<(f+h z;uV2MoQ9n!#n%!X&>vxPjUEc^c0lalQq5JKr^RTDygs+*g20ECaRS7;#gP-P^}wT_ zt%p%L9mZUTBAFEmgL^{{u8eg4I_(nO?9g za>-S7|H*`wzBjC1eFk0TA7au>9mv*GaQ>~crpl;nzAks-fj7uvmQeC*K=1nBNuB@H zkj__2zT1ELsVZOq0O0(GrTf2&ga1=NO0iJaK0+P&wo^B4g&B;kHTHvn9G~PLOjXYK zqoFQ}0ZBqgCKz=Lgr3SUxl5z5xSZacZrS7-!PsQkQdp7R+`qV37(rIGWV^mp{Z{q4 z=wsRQc*c3--L~1b$>=`*FzKD*dHwyF^* zdgn_wIN$AV2+)wtoEuA*NG<`wCbj%(90n^0+Oz0^sGh0is`&L>h|!|bTn5IRk>`_f z;zoG`0}rAM^K?S>L1ahfgath2^O!ep$wO73&6kGNgf+1*{T zpEDB{z8&tq0*{g?RZ+}@0h&5QVkF6a!JIP-B07Q)i#5NGilt@9lX|$u@T0kO?z{;M z+uyWdo!=&-+WH9*X;me@FS(5Iu4e|6H4et<=fmc7)%Cft=foCRBDswAMe!+pV;jUg z*kPC+UL)Ed*frwee3cG$mzS4^YWYgJ=8K_4E{^26Tw9j|d$o=54Oy1T9jhWl^E86> zfDPBCtRyE*6#B`wXjfN?C0SLI?HMX!A&uwy)c<_OKu`s?I$qhK@$@KOf!EA*VREc^ zE>-x>#CQ_lAY_xv{`P^mC;4T=nq?xw|v9Z(UGmH)G*Q*B@ZpSG39S9QIm4k zmh%W1DZkP0j*qS861Lb;60u18#F#)urwhFa*T?dOR9P(Ic4MvXU&wM|C6Qfo%8Ai2 zS9fkiRS(r<^;8+RAM91U)_tn@uDLTIbb;QdpjC}v&j-PY{f9M->q*jN@gJ1lPsE*< z16!+SLVT_sx8VRF=UG6wVPzIv>x#vKWEQt8E%)q?t)WtQAFClbyDEMa)|p{2ZGFjn zCKDU0_pEH5S#Y!N4fZs7C2Qwq)-E03e9wF6FQn|(kGpD{ZUNW>c%5TWdQL`*0Xy~w zEt?q6yT3PIc-Y}QZozFkhoUI)iv{*7TxF#^$*{0lFUK~<|enb4S*n0;}p2T(YNoVwxq^lZI#cyG6saw^v zcx$O;+pw`Mh-rDThrfQ>vvbQp8`vcOD0jvS)Ks}Y03zPZA#a9-jbhYH)S~W z$BaX^z9lOjh-7nm?VN+-;SqSw;mzEyC}Li4*sRO8g~8{N6g zL;~MqA*^jo1I?aCb-7LErh@`)B=V&-bBw?C#}fHw6XeTEm@tQ^@v23^3OLCbESG`> z+98=LqBbiVD=8PH2I0uOBz=ZO5S*)wW^X_Y(ph1wnUq?t4|&{hu2Y;7>QqC5NaZ$P z6iOv+xD|6Nz8HijG!{f^JD-F-9u{7WM%7qhH&9as!v|ZrPFcT57n({s*;)(Fu)Lp{ zg2rCKk^~%>t=uu9d^7*trG(1avI$|yrCQlpojNFk67Qp$$r-ZfM4p8sVjo&t@sbA} z$DqTn-<3GbranxSiXSa2qu1j#_*k@b6&ibq4QWmG*m;@y0VJ%!%VvSpErULeSO$I1 z$}-#U8PYN^J-vYvGh7_qQ%6Z-B2OBgsah7vM!Vl%ypYy3qKIq-A6@Ck>+>cL_bN_?gd%;v9{B$=P{GX#5=DI5a*Yc*tda6!m< zHyAtA%M7!QB^fbFII`=TkCHm0>wmENs9mO>d`sr9$aRWop{u41MR-27UHcXq*K+E@ zQZ!ynSSA4}bwvko-NRAL8$>K}lZ^{fH>fv- z`_L86Ae}k6OAw=32#3YctP)d;cP&%C1I0~qi2rl%Do%lYo~ysoiO@SteCC9dl}1r0 zF1jtg+I75Jy0C^r95!N6zVN`d(1+-$Beq+#u;zp`t5%#G#bPn3ahviTEUx>E;Jr6&6^d|`iI;4OV}~~O1wGyfzr}PG zIz+Hkiz8PTs{JzgmMHvoj2)((ZzahZ)`6Y}myEA=u>8zmyYY3L4=Lvb&Aa&XSiWrB3@7y+&V1ZfTKMX|&;0h}_~y*4x(d zVn&)InOyPw4|6Zp`IaedoG`+TeC>7@3)v~!D~mb%0`l3TU6p-Zo)uc9;pgc|9&hnU zc2QN(rUT~OfLbI=6}dTXdBCv=&m1w3s+t{r024*!#bXItmjD%e5*-@Zw+^+*5SuOZ zs(Fr`s>D)3<~20!2B!(g#ozh`3c6?G)cbsh%cm|5FYqoq9J_RWG70_ydEtPZT)}C%?zrtVqmJ-u|5sgYt*V z(FnPSkIrEfvhTPJp^;1Qw$n)NlHc!CsO@wFt8M};&gMIWrWH4or|Ex>+7e4xb$$6p_ZS0T)5PZs&#~w(FSAScmSvCTIA-5Gq&tbSA6h zZiDjb@`y)-A*+hAAN&SsDBQEFV z>8QSh7@Cyw-=2zS8ftqU2_~1#J{?^6x{2Da9aYTxgw%RDBe+XK;l_&W6Azd+Os9Be z3KUB<+!RY#Xp_}UQa0RvuC8sAE?Q8lSq6BptWFHH(VpO;x}4&iYO4B%x$0RBxQWK< z*p=c|LC^J7tUs6==%JWJ(1Sg~kr$1o`46ZhqYJf?>sF0G!?9x9QKv~I4hfGM##=WXYiiZ$G^*w%H4P7f%jlJpU@0b`NqFHT1EL$G zz;yV0kmIw=vCRqZd@2lgOi0EhTeAK7<#h5xM&n_Ae^^<$0dTFWAdJGcD7059Fi;wx zwJvMT5|j`EGC@T}-Jo(+-ZYr~6*Dv%|97@QC&ZxZdc1n=0e6GB=!QcuUFjVjGd{+8 zeZz<%nJzdv1Ln@Vg%-onWdjlXE0@$bpydrkqp}0-hP|yZzy&4Jv*r?UK-GJ4EzXNz zyZyfD^%At|hJA2!0MKHfr(TF(7Cj@+hXB9=bsg6AtZ!~5EK$|3PqW8w+- z1|C)=JZW6cg5(YcyNKBY_%ILS)tla$jBBzKRAx}v>Y zBkamq4?9;ii*?8_ApC5n9gH`p^H`3h{1x56@iz|mKz~`_NmX|3Xz7PLZ=A2wqf{@A>j+-DW0u@ftON_f1id_%&Bs#@G6HzLp zq#1crEedH!3F$x#!t$4YYi9dT#@lFJ{&(Rg(^dVU#1j37jQ1bbq_l;Tv&oN-;D6`6 zE^-~R1N;b?uw<^2LWPlu@PEk2Ezv{$m6It#=k+c@ttYb@e-D3)_JQ9jg#HqPfQ+Mc zRV_$c+nT9)h6h;TkaI}ikBmKQ??$c@RM$zivBRpt6Y06vKN7K7ea*Li%23T>le2uF zah>gmj;Np|gx_wmZP!h;cbfeS>2ir4VqX@fzDbF3pv>UXgopsvP+)b^jo7)k2j7qy zx`-7@6|1(+G#jW7XGc!{9{MncK<~-fy3wruNaRl?P>n~3jt%s)k30*dtVc9M3}Axy z_D`_;*W~<%AZm{u9w#-qFt4#K_se@c-Do{d%%n;ROQ&69U6?1(S6J zqZ0-D>!1G-zj%lw3T6sB#UKhsw-SE1=pR4%(P`)kW=p`w(bGu5xkSrMo)jlQ0FckY zNJ*|jPhCe77bn0~!9w34%vVLh$lk==L!pH)0VxkG9_J%#sN3G{tBAIWWe_MYE~?m+2`JtNqS~})||s14IbTp z80mklIsc=-`!87f|B%_E{!y>;qws7bg9$dGq8ti=($1ohpdbbkh0prS4q@_drRy(6IkB%Cs!quQmQo2p$X4_NmLDd+(!VClrgxkEm`Ax(OmL2%~v@<`k1NUu! zQRuPu2*V6Mgzb^jv*07|QiJumg#I0cJ|SqBEc4dTUpvQkT1}wn2@}UUf&f$JZxK;7 zKG$vk`?XsnIon_9a2fBnOj`{$F_SqMgE_uU1`Ex#yFbkt+ku4fDaIDB#=~YK6`ezI z0_X_s8EdCjTx0vy%dwxvVfWdp#}+e$8fWVtnZ~gE^22WQp*j{1sAI}U{%oeRq@X^A zbzxr}H<1+>20!Z-2NQia5B6N2Ufi6HZWQ_7nc-R}?ncd*F)NUnvJgTT(v;QAk?-*WLyKI4`W81ck zj?uAg+qP|WY}@vVZCf3i9ix+*-h1DDzH{$+o;822AN9^rHR~;mQ5Wh0$9ceJO(++$ zWGjKG=yvP~FvO6a!MG2~R|~Ds+N3{CjWEHqlZ$p#LjF({pdYKsjM<#ctInj(w44J+ zeGS1zBhN}&!nLY&!n{%{JC6w=koX?|)^o3LHFkq}q}8ml5QGvWS^*bvNikLVCBLF` zth_Dah}+5Q1e?9d=vZ?uA6uGv%(0R5z0J7)yJZ+=8yIz02hZA$V-(V;7&7G0euLPz zjBG;TnH~;J<^PjSM&R*A74yhLIt0cu1Gc ze4Dx!bRZ!l^<(Bu(qLP_pOYvK5$2!}{o5!tlGE~Ur8;hJIt zAxzZ6F{Lvp-%hX2=n0}JcT8$Re7=Tq$FDqBE7|RvY z1hIrR&7Ck(9_#8cqSBs5PRyP{e}=l*2uh}`?7N1&F^{9jLtPz&=bOR_Av67%bqTR^ z4dzeC@#>FD@Cl8as|$S($o%dGVak0|%$=99XMd_#>+sft&At2K-!!2#4y(BV^Czb* zE*^##Yc)EY%W+icNukjt&roQDyu}$+Ub(*72m)Yio}m@GXFRRqM(NqdQ1*udI#_7t zAVj$=Rz_l`lU(fE3-TtB-XRrZkSBr3_o*`Mg%q-%8n04wsiM@HKuyFZVW?O`lOH&% zEINv5`AUV5mh$0c3o-9N#|8VL>w%OGt|=G%l+ zcdXOucU;RP2$9?5ei1aAx`kEcRgp5)@0gH|S+$Upg{H~LDx!O%Bp+7~=)?X8 z0RLLlq^_oM_hr%Gmqlp*^``#-@P8YlY!qksJUs*i`ajmZfCSU}eY_iBf)(nY1c5-n zze6Z~VtoXaf9j(nQhpd480+sC&VHi;K7@jZ4S_AdWyZ%r$F)8}OmXC7G%!Gk@^45C z58W*UvFY5CV8+J4XltS7a65Ij)^>C+2z8qy_z5MaQ+kF|OVf_kHs};8YP+4D-d8U6 zb{RsksvgSYltW)YZ`|n=PPn)`- z#U}G-({aD}M~5!4M{6p1#FfJqn+$`u-NTffQgtmoJg@qb-t>^CRz64&+dod}ubXD3 z8+^cgx#`gVO1%DaO8;6Xbg}*4iIl(Z-+0JFCxcJ4o#4CFrKGe>l=9CZ3m~3Pc8b zfkXS8h-ik2HE%o7buP_d2bPa#g6y$8id>s2go_I_ha*!Ztuaky({A0ajT(%|XzJNq4ybC;mH98*uIsq6>vI<=+PDGEYOY)X%RXy9&bietl|wC+wpv@C zFIQ8>%vjVRdRKq&kGX$&e9W}p^#kO1g@mJCq{!LkwM?ATwo~S~$wfaY7W7W}zy0XzsKRVeBu7{%;z*qsO%My2R+6qbD(6zm zV8y8Nz~07Xbnlovbj-=WlMbqst|)Ya%_+XKPDHnFo_3OkzST}Vc8D2Ez3~nT;#wK+ zls%Sm>7Lw~kkK53k=7NzKEY)^m8?7Pgn!ezwjz6>XbkUzBUUTiTsF36X*v`EXGnQT zVD1jAWqqbjtUa=V?N+6%WoZ-HmO8AL9!;Gv59y;3X;I~D8{b(XpRSqnsP3~Nx7l^! zs%0l#10ZybFWv?%TnE+qSvE4=Mz|W@}s7;X_*a-li80;k;ey z6kXo%_WEdJ1K{3hv03eGu~IR^QOYy&yVq~x#Fv3(b>l8qH`J$68p<#xz7$58el+JX zV$t}crRds|-GI{Nno&N^o9JM8dqh`$5SQ&}=!$&|{0Ag^ZXny03lFc3`(n_)@n^(F zU!9-64(-)yxh=)I7myWqdh|!EaT5Xt^GoUwGLspYl0}&>cVY|qT9{U;>mahLnyGzl z_B#3&POHeOu^nqSZ${+Urq*JX4-=%yLOnkS`Bb?yu|a8un%KhQckWAT6{S&C@DRmV zS|*h+p1luc=UKiaX*~0knBp@>v3O;)QvHmCmTahHMAM;3qRH{F-&CcrsJPj96+LlO zr6q+Bmr#Sne+hJ`yp&tbQTo=UYnML&Z=Tr9vq= zmHlyJ4U7e{m%*ZUT?-MMUcvdf?aiX3vz^X%YtFTm?7U3I649Ntkt)mT_~B`^%~e2b z`@}VM84)B-NwkVC^P|Q4^?hP}-@%IzTW={!K>8R(M#9W6_HrCJpVz3IbD>GlVd&sY zp!D)W>hUnpBT)KkTXC!xYRuQzoOag?^|ubX?zh)dS01jUmbaE6@Jkhom6E-sn1>-k zSqNE3GGZqZe+SBB)GgIwaONP_ehI0L3+l6iu_{mvwpE0%5)4j~SrSPAICFe5=jMVS zHyQ1_YyVQ!Jl{o8L6>aK4-8as;CPb zym+a#B!B_U9+|d%nyJ!oq7(;-%PRyJurY@fmSk7 z>FuF)Kw8T?e+`=t!H4j^<3B~iGQ4-3PvB&j0rjOXbZ=5P+b&F zVMCY*!@}0=ouJ5SK~H|FJrf0OM=riTaa1CY@vRK;_;m!(z&%=&QdY|epy3g?YZTR+ zU%z$LfWt^N-$&KV&>@+7VXV@61ux+qXaHzlK|*>>{-XQD5-JF^XJn=}$<5nB?(#n`>C<(EXCGXJ4kFL>Y8< zoVzFX05AAa{KEudy=>$Oy8i0#P~9s9{Q3M~1%@7i!QM$%0$i+*n)hb+Rnxgm&r0tW z&owM5visKZ_fU@=SAnrye#3ijNny+Df99k&q2L1$xXj&|O>WIjW!&q>_GWJXp!4_U zww`?N&=J)GGXBnHaQwq_u0ZWXBaap6`x5D>`K?fc{-Ia}rcU$*YZ8p7L_(oaz+wF1 zk9=#t<$|}H9fscu**{~z=7o)yUJRlLcHh#EdJs@!$@IR-AJ$I^Q9xCT(eX`F(7qD! zTFuRzcqL-CA(ABYhpG&H6UWSMdS(g;xHFs`@0U&}Qf(On(@o^CgtG^Wk*u$-cI6_8 z)77zV+>j~J2=TI|Sx*Ac`9Qjv8OrRhl5v$)pAePm59}Z1$xyB;)NhiWv3jh{9eh`m zv3(As_0{e)f)_BYhW1W+X;?xoWv#pJ23{iA1egEQus3y>dhX z$h?%Is9luP1+EJrNB6Zzlg~_;4-+1KBKL+uvpbj*r5H;8XnQh=^aVlN1a)-Jty+zO zhuignxcn{hpo9M|=~dL$W@BR&-v}UYR({$1nmJ_=OgwG=v0D>qn zC|h#xS_~!O=0#Z*vR*v+y2WVFQ4?P3s#8>1-?tKJ)*O0s&SB>bP9*$T!u2`uByzF@n2a** z0gkRw=8E&U$vvpkw?l50E)wzpHTW>WRKB;HqYs!S-pjK_13RuKtOZ@=_zHQiTR1#& zdw%e76`IPMkMJggGX38ZLUY4vhSr|(a76~X_0k-V{?LW_=F~Bk-x{R}j*lOb`z_Dn ztYDK^YWt=D>^)E_PqB6$GKE#x^V@*Se0(RmlBIbAOVSdOgF?%WblbjXRbuM=iLoSb z1S6ryBF{qHN7^g=31pOi=ktqhEVo7?fJ44zS#If z#d(So-A=31#Cxir6V&q1HX0lW#q+clA=2oA(Q$@~hsd7N#`hMlu&ZUr=k)+!yY_Jm z-vsZFD&n|OA~}2lAKBB&b(x<(5R$2nV$4RLc<4C9K5`n1g0POhk;tczIeqcJZ|zcQ zq{Zw2RYo1f5zLtKU?`B$g+isw0*kB2sr}fV8E_{%%gD5k33oo_tT@3crZTfQD^aL` zg(xwhLY$tzH(=&Izxa)=|+b(>y5yjOYucI88+)hm+q@V!d$fwC&GSc9iO+pu^o zdth;CDXo~3PP6%u=Y@n$^BH$aWM>Ly(fBmM67FZOH%|lVq$1r<=fg*aA?jG&l-4mF zxPS?%+!8Pi%~dw(Z}tN=$CQf;_9HJY>q&kaqaQfdb}YQcLIoi^<_RdLoz7UY5Tnq4bI;x^a?tlJ24RMB#;-#3%SRgax$y1mwa%q^B49 zx$Q4}y!)vK+(t?sN;v1e`3eE)2baJ~j4b=(>Op)4Wk)d*K)omqQcEc62l_{^1Uvcz zlVtD*ea`l9OoFh|sR|d|8M62vAYKVQkkqieh%a_;y{GxL;&JK|Ua7+NqPud+YRuxw z<6qoCH4>($9r4=YZz^ruxv9=*?dLQnIQOWJY+Pc$?pOa{m||};UD4_&pm-cPtfQ1` zAe)cP&liSc8$UD{XfjAmEG1~Xl2&7uR2rKUYib~Rl!$KJ1q(oLL#C+?y1!s)qN!&W zv$-YzW<1TiN}BVcz!?^?$)|G1AGk0u-XpnrEUa61Q1(X=SV~;=gZ01XaJ++B5AX(j zOGgL01o9?23rRldx@YaALXS8^jhSP)r=wH{Ubh?AGyLHliA7fng%pk`@&3B30<$m3=o2c3 zAIo*)H}~J9=ONuxH2zWWBYCMjTcPKUMU`Ts+;YQG}X}9>*XNl)tJpk&Z|&zNPpN( z`N4rZp_Sf2k98pqy1#YX1>Sl1ug~-yvfX&#O-hp^%K*6rZ@1-nD%win@YP~2`CumSKRG@zEQ6p^# zQQ@BcdcA?(o&c#|#n8j0SgG^U^gsHR9ml8o-*ORU_O{Qog_LDJfj8r$%*vL_K)r$Q zYoVjYPr@Lv)!GJh^+9Lm;KWyOxToi?az-`Q7qCP{Vhbo%HXSH)O26z-zj*6IvtwE3cc1kSD&_AIV8Gb081U1^-7Gbj7W~QA7sueD}q2k?* zl3Gm1Zv|a-_nhOx-LvKt`^;k-Q!Ov-dBvJ4zlmCW)<5|!6Ow)Q-gxl?m$g0*kPv@B zUYX~?olKqaz}18{M^ui^H0BNARvfe|Pu-QpT9pM@onnh_jHk|n!_SfH3S@W2eRt}b zw+A`60L>-HFL*`k|4`THM25Vnm~Y0WyOB$(ae>TUIq}QW0;w35Ln^}NK^M;32Bs;~ z>*M)@Q7Ig{0(`iCqTm{;iaP;c#gu~?A(_c3%X_^XASn;2HxWd$ zv~g#sIVg3isViFP!C5`zPSSd`K2K)fjLM}@C`3i+!H@7l6*4VAA4_B%Rcg7!`Z1Ka zSd*^{0bd{-Jx?SpcYF_9CZF*W5M=yYGuQ{m)wjIp_1EV=%Hw}KN6z*{SO{O$WZ186 zHR*o~dM55)O~Jp#WT$_L`UsmC**O~gcX`YwVas+w05N3dXvEbfv5Cw^Nizc#ngbp& zppB*?IFOo1gs@rV8m|#fR!K{9qgTk`8!~D#48DJu;H0gpZFs@>yt|p}(FRN7zk5bG zyAg0O04YX?nLgwzsYbKJx87kP9_HQjle8PG3Ns3xDa>VveV*8c?Q}@`=ThOUCu8s2 zK6fmIcYlKgt*m)Yld}87r>uOwaKn%9J(!LT$gmWs8hE4zjGS=7q&|36#smdP`PClp zPQ&}Gl6%VE^zImuex!u^q?ZqB3=qcj7xI{z)2TWPE_gF98A3Lju69!;=A)B)WR1!5 zi*ue*d1bR3hwu!KC+tfL--hS!x`6NR7|mva-j=b)D7JI%tvNf(q}RE-KRP5t#Sw^F zqJO$PDKqP|opjXgwzUPZVaQl_nCnjs8ewRyny$8ch&wyone|92(Tl4Y#ZxYk%UHa0 z6uS*DA+TJ`-_`Y=6whuJlX^;kSL9dk=8AvSVKBi4+Xx7bl_UIfUCA7gnFIDGFmW3p)5aYq3T#OhHOc@ z|M^Ru)J`-?#k@TVPv_UdgNg>f-o+HETGU?3xaB{`tpCt7XO%Kr;c zKa89$T>n+J`(KP&*-A2YU!d~A0XSrEkkTnE2;^JX6S=Hs_;UnM=SND42Y|$kugRng z4>gndfqozzOTt0I+aHS&ZEK=e$P1HNdziW&O^r-P|DCzP6Cik-V5}<_*NU%ZmON3m zM^MFbtFzhZ4uAkIEq3rH#!mL5ri61 z$>X>OcxUTojhmn3ye?M3_s5^-w#AoDw=1e0!U*(wV}UYr-EVQEEULqtH@|4&nU|` z$ul#=1CT$?8U|rtkjkrB;QoS}&e|nSxKhvS8kOtap$lD;!ip#gGqCE33~pab(|TjZ=olZ4(4#+67B;t5y$}8!K(fs@xaVT)Rpb>2kd_}!#PH&$XVGSh znl%nS%9XZKCw4JEXBi;Td|mC|w@T;sFokPnuH9HATzLuGE@pQ(lLcA3`^Ut>U+~!9 zxDKCvfd}p@8WH|q;Q3qaRWfmQvHy3MD@5_%pxJM8Wg;iDp<*ox@dbJj5bm;vIx7Z) zq9PQQLL8|EfMKlQRIfDoMZt&@-hwjgn{Th8W_!2u16#3F@ z+cs9QGfAN!vB{yonD9i22gOES{s`)v8y_82-;GY6DDoI-lU!him|-C1WL0hd85Zn8 z60)s!OiRNd-yVAqwfd-q*ZOdv?2Sa^J=vIbx@C8bC;+KfQoiUFG0L32Rwc%lL8NJp za<e@>;G_RuxkQ7T38ujTonq`Z?yKfet0yy;A+cL$8f8~!$W2Hs+JF8JqPM>|(I2S{ zN&X5j7+(tNcMI`dX)m+E(Ls~KEc^shOg?Hs6<6ccs}Y%LF|IqcWpiiN zv{~L9He$cIck)hCbYVbkQ{9S!R7_wD|$esfsRR|{Iq#&TfX}WD3%<6 z8)zWMm3Oho0TrF*fIN_0=w9c~&$)EfO^Z;u0KH;)DZqMHp1((9PdXGKhO>1CbXOMm z_c@t0Wf4?|m~tC^SKfI69Pu2o2&^1WR`q`1u?&MG&BmcqV(%++^Wi+Z=hqd zgk>V@+Fth|88*;dZ|w3gm94#&!vhwt;LJ6yYc4u4L;LoAHrgfTunOGC9?HlweRd*A zw}utA<>+Tt5;OF=m)$v0$ug1QJ1>}?h*$9Kaaeno=>fJ#=oN3e410S#;htNx2oD|#B5)N)FwjB|La zY=mDfdMOWj4}O#!Mq;gox#_GPPw>*TVUi0t(Ho?t(lO(=^|c}`;CV{@bz`MLY1oe> z1um{TZt$G6Xz&$Vynfq4giO{FreF`^&F`Z_`)TNo;Oq_)qzO#8P@Yc)=w!o34*=}O zF^**GTQyV1@ci6ednezQx7xG5Z`q;ZOp&dIXHd(?Z37F;GZ-F?y`r0Bn_a4bshDhG zKVdz-qq7dR^Wdx`Ra{0ydu8xnZSJE?GJ+_oLnRF!~_B>4jKyayhyILAy8_i+#C_+=wV?zgUFe zdM^>@sC-Mg;#2ErhrM}d6XO!;Z<`q33Ai?CB<5CNwYDUBC8ktkF{wA!>h;ajyNxawdQ?5>aw%2YU&CIXTSt?eV^8Wq?Zui`p$6F$igK3E!buzQX+RLCKJXA{4!bGYq1On5F09pGDdP}Q z>=<4gRl-}bRp2X25o^{|HjCYM$i1oMGnvF_hjCYaURNhI&HHv5nCI^M4|RsW7}Y+D=GHPH_B>AR zcHQ-N!uD$N{o`$)!XJxAWj4qgHiRP*S}^TInEh(>peT?ksfZnKAUaTXGr6zo{%Eg_ z;vzyX8e1=e1_ zE=sJy64vWo$au|%DnojQ3PW?Fa(>lXj?y6mTzT_TQxTO$bpZkb*08K=O&WRa`B8$> z6ZR%D!(&A!l{r4FkYg*Oao!7ZbLcU&O_Ws>%zBfsXHfx$1ey?L>~4J|^N~ulfRT7^ z@#7@RB(~c@4Hz0bQ;i{#T)5pf6bE|QezI9VqqnWjQSM#Eaa`-{hz z_v7yf6kfNH$I11Ky3CLE?nk=I$g?T(R!i7KjJ_Nxu-_Ay#jSHfGof=r`D^hQL_G+9 zj3Fp1NTdnksAQXZ9SP=K!e>pHHw6#Wn4a+P^emWyQP)tpMqT4&1o+zdfN@DjSxuV1 zLk?l}J~gz-(iNo^W+1*eTmD$!w-o@fyH+02+iGlwc>@k1YPgT%9^h`YxPpuNjW!95g zq?4DOODr^lQ4=-um_m|B>15_rf>YB|P1nby(>xZM%F4nC2^%6L<9(nC1hw>$DbeM> z(bcqFNA7jgL>BD$U3I2QFRUch>RkgS+CSH3+7kzA`CK7;9c5@nn%n5ZE!uQP;C{H< zchT}84@z#TPp^r&c+-Xh@zHOvpRUsU!ZB&n2$ef&BMTpYpRQ&G(1{Ojlb^-lP|#F0}PXF!=C5I}DUAHnMQ{Xu^41c!mR3!6KAiyrgT z4iz(Tr}Yow!bczu!~uD(@jJ3FKthq0M(NfKCWuHweFG^dpE;wj#XX<(}=FG|a70q_3 z4sXZFkS7l|;&8d80VYe_DJ#lQy5vWL{x2+NShZK_bH*_T*Rnb&D!#I=EnwUNMgl7ESyHMC5qjsT{b48 zmpUTF4|#7dB^IVjJUm_$`lWreNO_CSY0$vORYw@i9v1z4J$f=*F; z`XFO*LjT0yklyGoYFP}Aj&^qh1VTfqu&+s)WR%!U6Bx z`T=o44rtqFjBv&_9&Lb7a+a>6WC1HLO5hEaQ`ebx$c(!Fxy_kFF&?1>so^s^ZajIJ zM3h#4VYmeMSk!P@)=W;%smF4Qvn@sw<_q`{T}d-Fg3W&;pko{j}C7GTTPk3c|k1|!vcnx@x!>Fg18Wf}dixDiibHbJ5_AYTNGuD9F{0^X zAZx+vCh-rNfZ}aW9IoKX5lJacyVi>u!Ty<~NI&QNN(T>5)NQnggZNl^H_WEx5Dk|2 z^u^5OhwHimRs@rBL-Nuo^N*qUYlW!I^|VZ$g-8>pEdkwZ_*2-mkLZD}n*73n2UDA% zx9(rK_@)$dv(FvI_KuCsnE0~6woUU`=uo$M(iI*R`taGR(-5+&*#U2fMRQZ`O5#fK z;^|B(+}UB9{rhD~?wcyI>xSCmKo4SfQHk0)(e#vk5$wUC%Z1mSwo~=~{J^TeZ!bmC zouQ`B9Jr?SB{-CF4%DYyw8ma-v|CfFR`N2OzGKDWZCV`R)35;Vg3J!fx9BNUA7UP0 zsKeqJWDTUpmkLpIEY^ZPvOpB-NPtKq5Coe^8N+TfJ(Gjbk@Px(DW_7n`Wv^vpXXDJ zI^m776(LpVyTg9iA0VBexTD=r_I+s^J7%c{V$m9c*9Qz6Tr{RX{FY_}xXUT0`633; z4bl;a*(dgTN)Bp^Fv&5@xpBSL#s88KyRbfTi$XT9G_1EtSVencDFUvCaAW~oezY$Vfv{d*c#_iSo$foXSSYiU;$GV$ejje=9g3d(+ zo{TeQ0AsuQkd8JKk{!xx)^wkx_-n4}Gw{NwV$RohHVI1=L$fNMG6 znFg>>qP zCLIpel^e6k5|7hMauPH~G)7_555is?(e~X&`i~!bfg?Gx-mFka~yd*`=KuBKrrS4D2@<+JBS@X>*Keh9YE9k)Slj= zN#j*3Kv$bf`63wMQuIkvdBhgBr?c_&VM3!$)52IVG~ubHl(V5SCz6$j?$_00X#sC%xqSX7NAtBHzj9lz(u-l#z~PBfwT#=q#S96~ z5fKU7M#9AiUTS1+u-+3Z3^!##x)2i510!Edk0n)^L4HM@>e#xR&jZ%Zw?M(>G6YF? zUVY-#X}p_}&f7AILHjz-RM>IAHG)p2_Hse3EEEX=p*hTdVYE?g!iFGw^Kh1si=2rhY&Rz>*gxQ)g z%klZ;mh<$sIm0sZ=X$L4Th#~*)MliYW*ycObUVWjN6FDYg4^^5--xcoce35(M*s(( zTMnHR-$0&D+-u+YNpY_em)=mGf%qs7gt+)F9}=k$^;Q<0yUyNT2EmTuYbeop+(img3= z4voFzqCcTLZ7bVPh^0VwN0wu=^Du`wpa9sNeI*U0F5cX+W_!=FxWASVbGdfbx(_YP z33x5>Sy);!+qW9={5GFxolFHdTH0Usq6>fE%*}R=xmx=*hMJs;f8_kZHr&{hkQ#Vb z*rlixSUK=p*->lvG!Oj zcyTW?U8u^k5HB8pQOe;o!gK55X4a_Ofp`LUjliGESy!$n(Xhx@b8dKqb(wQjup!&= zw@~WfW|=sTvG`%7a?DNVr1-_F;--QS#xwI^K5$lfM*40O>9^W+CqiA?C;xW2oRoam zu7ktXe6sN}8K;z0&$G~~W2R5aF_Ss%9GcUTZ2?xgw&>gM(%M09$58E*E<4dK;-ER% zQrga6l<~~SZ?HQ74wVLS@f+d znz$UZStMeT5@t`58d_Y#cFOk-wJ!{x3vT1mxm)kS5GzTx+gbzer3G!89jOWj$T zn8KWSddvHl`lHO3n6AuLR+jQusew)t9|+sd8cB|qwjs81`OFIPs!|hjugYP!MYg{uV<__KPiNqDJxSZ>~VFhT@&f9D*CN;jMzy z#!#E3Ao_FK^Fclkp;7GB&`WUe`lVjJ3wC>;S|)q>(damhPO8{ztY^LkY;-$pobfB1 zjYiHxKzY{rbBj{b!B%7R@}{{h-MUAz$Q1JHN77#jW^mj#aNl5pk=v|*+?`Tz-_YDf zWxD!>UZT$BUWS9`R7c!gY5;dUuc3$PS(}Eucu&EbqW5dM-7XFhHa7GhhxhefyPM8{ zUwdxYs=8zbaMA|ry$VEbgfr*7!JoVzgv=l2JKuS%cH{~!bK%~>{Mc^TdE{!k01}E8 zAbx7&rGZ7jXof=bo@I+SJEJGv_4&8c)3OTj*DzGyH+?HsTga2Cp9I%h9SQE|Ka_Dl z8E@p?CwhBs2?XJ8v5p#L2O?h+1B*3XKDQ$ZD|nrS*LW>4yeAYwuoSeM+o-oYNyxCb zf-#0Lt@MWNhZNGl6`Q-Y=s$-}N6+*W{G#P+&|)(W4=0fD`18|Rz-)3sq6@Wh zD`KBDA{`JTrgHa0D76vv`975K%|&@zeKqV@=43abK1@7z;FsVImeW!ITD zp%K!r9G_gEBziJR7O8{Gg5v!Q3)qex!>CST->akGB(@Y;BU7^9bSs5l?^cf*2~{X5X5M+t1STMBKDJ3}b$B3Gg3w;<4E5e%LZ}jFM@`Q=H0J_*!1glzWgUN}e1ou>sq-KRX zvYf&A1=Wx%f&^8mn5YC1^N35bv46?BYjj~7Nk%vr3k7VnrMkg*NzF=`$M+Z)1TR*_ zSw*c*SIr>CCi;OlULicE2YZ<^M+EzCn;=cRPzflMWLBgaT(!h; zA9S^>$o$fgQK|rT&o}eAiNRkCxtXY1vy+k5CiTj98)YZ@JCJP4i`273>O;;!wP)vpC;+GKXW9$FhO= z$aBSsPW%Rfd4zhq7gn{js;$CxGx4lICYB?T)9t5>7ZTZXM3fx6iY90*nXSy|mT5ec zqg<5WmP7Tsl$UXW9*Ao|vJi_bP1wD1StmHo_3_VLsf2i^`5eNtiAP?2(p+vSl`75x zSkMr%%9RoLj|CB6Lw$-bk#(D2`-a-q=<+{rBil^Term&MYk}3!S>t4Q+lJa>HA8Hi zd$3N2q}QA}$Ph(?BZS=irfe5U4gS3*Anc5%dA&?3S5P1g%ohpG8DPXwvwjV=i51aF z1>7!vV6-Ca%d%Y%cipcgbN~k(=9-mhbfLq>w?M7030YDPlSC97Dp98C^&4FR5Oh3e zPwAQ_WdGx}p74+?*_e7dTlGot_Kl-aR^j$-ZI7UAVQ<1wuHwz@^B<|l-|aQINm@rYs zCSOiV$k<{3K}Y|{({GUvhgS%q?+6iDQ^%EZKN}BMoL?09YSai;8s(U$PG)9KeHb%Dj zBD5*;+&@`=6;&2%S0DOh*esti$X(q*@bwZf995dMNU+Ogk`8oxyru=NZ$!Jx;IlU# z@|?Dvz-L0=j+A%G@fA%ZnVSlBrmv`Mq+OP?xjcxbXsSF?5RrZ=xy>s#@S3O7oD^-} z{Q~!7sfgQ}?nsCA9X-!ZsaW@*er~P2Q!1}iouhIztJ)}Y`>lNG9JDn&zUEZOR-GZ7 z*{Viso=i~0Yg7F(0u^QSooje1dlI3-b6K^8?b_G+>{v88)>%Eo#IluTaM68t#$fIx z4=3i%-kcB$`BRC#);8J2C`YDPlkQ1IXWp?Ko6fMl72`s_nx~?K?WJ5^h899waM(58 zjyvJ8nu5CEVg91n;!s1w5c)t2c9#`hzpl8sV^u1luwyY9o!U<3f!1N&{Zfmj^))Z@ zk8^Kwh=z$uE2F$_*62aJps@GHi~jEcx!-uFnMvhD-^)v@+)9C=LsV?K>}Kwbs>qZg zI%|BnRXDA7t=9(xYIb+Nk zw>5uPFO#~5<8jbqKP2r(eRQ6H+5AB=;1CxB>?PRy&5A?$KyM^VdUUvjkFEX6UT0Z0 z_dWeNEfT-_(`)=u0S&cRm$XI@(L4DJ=aJZy+`ph8&A*G6Q~Q!X-= zd0*-lciRg)s#i6yLoshV(N^?oD}&0HLk?cOU@5EEVfIrxI%P{Q(B>!V=uv@6gsbTBO< zzJ5=A*mSeWb!B=h5BaHBCFlaMl#r$ZNa?u7`XIz@?q5^a#@Ej8nKtNJ)-+L1{l%y8+k$ zZD7|If4>LnA#hb3`^FHpfqhxZDFK3Yfipwpz1||DtUw*r%yDB{V)@}i>Z5W-ODpJU zKbIoraCGMN79y4AtZC83L)G|?b4kz=sUviW^Dyu4K9mJqfekzG$xkx+6(2R3o9g56 zMIb#k+rgOvGJfDNrvNF*e0=V^r`vf zA8vG#M+5;3P?(u`7=ft{9E5y0%Izh)39^LW&zuGs(jspBGrim+Fj^L%?Dl(E@8vu4}e}kuzK1P1e+OP`xY9obui5-f2Cyq`5^ZsxT&~lxP7wr(V&=4eUBI~7`0}19F zMF7o(+0km_)CY1X%=m+gf%^}M_YO`ncZ~f1r?u+hjuq)Ds8~=`Y=9sl z9Yhf<5dua-f(fCBEuyICf*nQIin4Y^bQKFLxULQCy&_lu``YVwPC`N^cWwgyf1W1` zy59HnGiPSb%nb}Yk@DRAk9gaqlJ&no9XLiTc^ud${Zm`xxDjpZzj?ZC?Vf?t-LL-_ zeX!-CTW)+y(+2ukITnMzES%RYW4PV%FLgOX4|YgvTXKW*Z1(Z@_ZBCuE}E*Jm$1+) ziJQEs-sRi7qixUI#}%a|cFOKKb6ou>(~+M`3&%c=PI+6}=~eu|_lY^XTeqAzE0c3T zlsWVd`(P8ZC#|nt@8vc}(848Z-{upkyFwG{rS{!uR>1A|I%a3EUZnH+ccXedE85m> z;-ODb2Ho2Re(!MJYxmzS#}k?c*{^$hckYK}k`4)1y>A^Q#U zwvBTRn(P)dAb-$KXFey#a@I#T+Yp1juZv>i3tTeJxr$mGT^wV-dnYeDIw--cf%IU9 zVw;eEJvXjj;u!Gqa%hOm(E9w}4LiMUzH<7J1?jgvx4~;fM8#3FcAOd>8hQHU@$bjB z1P#jH5p9=ee(Gz@{i3cv=W<=T?VQ}MJd(R^x_!&C0p+Q063-l3(C}EjQQ{mIL5s83 z{u0W>&HL9f&puPUD|p59kX;@dhdkd) z%Zx79jl-NT{qC32G+@hm)8w8D_FvlJ?qgJx+|#*>VerwMAxkgaH4C`fv1e)6-yb%_ zw>BGc=9bMmy>DYh=@;F9OBuJI)VEVuOE`q^V&#m7oCL4HN6o*#5vKRK)$KrW5PyD{ zmv4f5jp^HLW(&CoQtY#8zPGwD?AAD=GGb9xBtckk$8_IXC8&+*Q8_jmI4w3#+eu=#RyZSK&=Wr2nV0v7q4nCR`| z@Y#3o6ia8Qxo0>WeP!!^IOL4&{$Bd&xto6bvO;*fr1P>fds53S&b1iFTPY}+cWvr_ zCRZLmdmGtg+yi}~;mF~AgOlw|mWPW&|(o4TToOa-7?8sPn zpUA@e+%4z6F7VjfFG$aIuV2P`W1~*iLf+eTM%tDlx?bBs}^q#luK zjz>n%Z1+rW$-BjS4_&N%&c0UW?DYAct;YU#-{gZ;IB)f|kc{?CkFT3|eZko@yWuZO z1b>8Qn>-HvC#_ZCwA*unXGXoZHLm@d`>F1=4Li;(on`o=_af21CansGe>zZe`KP`? zXPfPKIVjU2dc$95v+QcW*IPZHp#5yGqFz7kS3DdTbfJY)#@C{fBG(I({_LILI3nA8 zX8#fCt-pp0y%+WBTg}1?%g#M(F-y;`?MRQ4U!_C;NyxEjf9a-mzTNZ=-^%VaecHmg z*7v}dOFTO!96H-~`QebY7jM=W;~4zaYm?E{DTlW7>?C@B$$RmLb&bk)#~7p?OpAJ9 zXH^p5{B213>a0oTtA<$j>N2idY^_JvZycWzYx-^e_c75UlD!&!dUL<|i2hCvjXNH? z5Yqbd*hhsUju|-Jb4h&W-tk6ZuB2y=(LD|=Y_;OmB7RrPSpA;v7o*xqZZyC6qpagP zKZnovZyi}+vvzljPy42}eB#jl?6g}yGh_dp`$uHRmkqc5qzzv_GxuNgZr-V>slo)k zEf@B_`m%G)f`?_zKGqC1{Py^UVZ%2@hOb6E-DJ74cYs^`jiap>raXyW4r^=w<<~hb z9sjbl*_Up{vKse1k4cKJHJSa2e{}z~hjYiAXf@&0%lI8RWmg&*?JIk*`1rDfCgZNg z?CN>rBZ&=^|v(Nf%&7A zZw#vYdfnqgbr#(cU*D2)CZL0T=E2M-b&mBdI6m>W!L|JIEeB-fdjIS{w~gERznZx| zsBOBveM3*1*q1MZj$KU}?bW(oXzp&Yo=dH6FMgBW-uRpTrn36hcP9B>ceug-tmpqV zJ;!NjVZp?Z;n$a)8?B#q)Az-eHLrim_pDKL;@kDV^L*b0IgP(jKJ@hEiGzg4j}#pe zK06;<*z4y^>y;hDtsm$&$_p?rs%fv+D{M&CsI(oimKMX~j(n`K;b4QJ{&C4ME)O~t zopLoU4Vt{pX1&L$8@!zv-7X(}vZiy#dTxE29qHhe=y1&~ElbDQVQwe1{JbIBvgJeRjVv2Fd=x?DZD^)IXL0iV*`N3gY+|Qn+~3O>dM4vqntjED}NH)a#HTwnx^-MdrxufCAs1DNt{&J z*6cqA1N%c^gIuzV^*oBtwl$uaWcG4v6z|2$9Gg=c7Y6T;UFW9vdDg{*~t@B_Zh@LyJZGGra#H`QaF zj$XrDUHk*Odb(C3a5y(%{X_W#{)FbaiccfXcgOeeyGR_NMw7$A#v|pw!Ji5N@Hc$I z2)Z=!3HjQI*5_csrQ(Cn4I-WsO9ea$@icA`DgQnGgk7SF4?Z2K@w5=`ebN5BiSltd z*MCr-hj(OgBYCjn#GlFwBe>Dj0lYh);)C4Wp`t~K!gvA#MN_7xN)S0ujiHyZQj?L~ zXn13c4xw=IO7TH9@V3!PfQq|u)NPOVpeY}~pT?>`;Nw$hk4JKa{3xk_8x4oR2w5~W zLSpTrVF^Xi{BV9K6H8Msm0pc}l@eGnTme4}&!B1LhSY2u=pwFEjC@Z~$R$mqDGt6F zLh(TsJm>&bvQZl)kle^gbR>nbw0p2A0hFs9NOwTtc?rb_CH$csP7)1`w2?evbahyC zg4{w;yHZx&@j)6v#sbHiB83KDk}>V(JVHmBAmt0fbcLLeQfqQ^z9*1)aZ!*vP)!=% zrRm`K?s~-sb&qfEtJD(#BIZe?0u9+T!AJ*-U)?68o(coDkACr!MI9Zwk! z>WNyDIe5C_gZ@$mf2{6sYoMomCLG7OSdn-#;R~7pqpu_=_l5qdrzxX~CbQ(A6rp-l ztutR;R_&BmsvfWC45@syE(a+$i(9eqEiju5V{j`K(;QB=>;ybo=^D-gu0-MoWrm_T zS`#MhzeA-@3^|;0;9k&Nt@>ok%AiN35=Q;)Kbz}MSAy-7DdHkonYZC5QR>rOZJVk zuJ<*_?oB`r3j#6)GKVJQ5WWO2J@yt3;Z2Z6&=L3Np1l4R7!*PBvmy|;fY)mxy1*H7 zF$}lR(j$_JLs|4+ELppI6>!N1F3kzdJElxbJkc>yQ6MSL|EOycU263|XJYDu2edcP z)MPd&R+Gs9USt#;Z5PM8if{;E%8iiS>a@C*0>#Kk$YrE)YILDM!ayLBMq4;3Min}w zslyd3ys)_ya9UYGp}jBLG9mq>LiAt>6AOLEix)BVAYi=(_DDnD>%qiQsSqkWnhp{1 z&YRwTXTZ?$U>Fm^DA#&15#iohDDQY;8XBrEcdzM>jgY$DK!WH%pxw1+qPf6W8VZMZ zT;f7`QCNL4g+|aq*HiC}LFiQQMJEC@Z(tQbEf{3{m6Z;Y9;zhI-FG&YUeV}U1iitI zOnPW7=ZRcCg?R*<@$$u-LBKp#He?|6cVU9Lh{YnYvq&HSy@;q<#)}-1cD;#R4~&(; zm-MLIp-fEl0`{bMXmb4IJz)PaATWuQ?#9GY+MlktwC`ixw}YI<4Kx)u*PV%_%4~En zzl^Zy8$rO!@HK_6@{=tScxwS;^UN!?gTwa$Z~K5ZJXWLpWXn8!m~a7#z@~FH)h^eT z3+mbj{sA$f`ee)cj9@}Byw6jS)L3%{TKJUlw9{Jw7;ly}Q4zqL@q84MgeNb0q9}}R zV+^34KbkK321x6HY0{xD1~JhpWTz<-9eQm-$bwe@b_u{b60Fq=VS;In3~Z?X$O`(n zK$xUMVlyL92aaW;swXzOjgB6e{Hy>t+`||V^T#vs$gra$mK-rI8U@^Sfz)P7aCkY4 ziKya`Hqf9N#~ttA{xJyb1x^IRfa;Si<4RV8`oN#&jTCH z1p;JHuZd)WBd=aiFfy@_(a?a%-@O2*FUW4AViR4lzepVmswQcqJc|eNMX;ElHLWA? z-{WR%SqCQ4gLN@y0{F@lbzsJedn_vdM)j(5iy{2*aBwy;xAMj3>g56wY-%-$Y%l~gi}&c55KO<0SI1CR{Eh+vs+TgH<0~o-?;F<95zvML zTIE~{bt_Jwp($=rk^fg->#~&zPxhF8as^y-hW)=zqv0yv8Nswa^7!cwi_3+8wGlK& z4q($~(6D^CVwfC583W68v}i}n!q39iuONWbdW~hM(E8vrB2%z;ES(poQvB6pLYEmep;s?F}H?Ly==ippRWnLsz6` ze~}oXmI8^&Flv7D-vVIxI77ip(`b-{f_u=F5G!8R2QOE#W>g)USL?*=Nykh+|9#JV zz-$kE4kkpuw1x#!O|c^+yh`0FyaV2c1Vv6fo!YG;$FUtj0JX9OA%WipU6^#lNmizlu7ge%gHA|i+q$2INOxqmFyR#Jlk@=e7N7@m zj5vFMhOSaDu5DMkGM)A4TgFeH-(@d& zqFsFsrw>7DD!i&geV(RR<0nbUhf;p@#7dHMfi5lRGa>;f6d7?i?gW)t$8=MnTV|vV zx;U&X_OY+A43u~fe888W8+!WxhptxTUio9?T|dZ1V&^HAshFo_GKUG&@xsB3b%0#}Uzky=KH0L8 zYwR=>PUa80C1Eh#qGWz#f5^)X+zF5_QuH~Tfhu6k`~~3XOgB@o?=>5egb=7F*JL z?63;$uudp&kl}u^DFJujgoC4!{Pr$p2X+*~ehydd6BEy3If3Tg=o(S} zR?llTfhi%3Go(T?U$H~0JdUYl1ohqda7{1Zw+s|wuTlqU`DKk7tRbTUwU8T7QI1#M z8L83uU#G}s);QpQGQ~W zz@O$TK({#wTIx<{>4qUYm0@tufDc(@s(dq4KE-4Tflgx(_#qbOzLx-=9Ojdnu)}NU zhn7(gytK@xJPFo3PPf%8SX(xyfvZMYtRa~EVxAS>9GEx;_}u~gniA?LGEs-DoTgO_ z4RpBmL4J!KK@RB&tGHyPH{DzvPPG#&p9oZ}qOjD|0q*xYW7onW_`XR+o+(8`^ zJ;B0DB&5vtFg)UKX57CG4@U!xg$e}2<4s3(7_qztvUTMPpv>1gTF;wb)YuV{!yk<_ zM{9j+3ZmkSEK_gDTym{fXGQ^IA~3};g1H6VC{S{(fk|L3ILw1D#Gy{YQ{dx-n)qum zi?`wqh|NdCLG{7%>mcQm=5m8tRdbZJt)M`@0!ckXpeT)+UrF=@Jls-&+FbR?maXhf z!PFuOQ&q_Gg^|8H=L2dse9ctoXh-|O6#OAPNvN1Vq4Ld;>il8^ED;!b=7A0_!Z1zh z;3o}F-7jEEmCobyL^J>8kjBnH^+FD%bznNnrdNNcTrkJ*!dyk-NIJ4pV##a|aFqVw zC}f3LJe-CscV;FO3f|Xr<4*`6PCbAk7nUY@(xI?|W@4dHE{Q%~Yy?=F;Mx1*MhPxfXEL?v|A0OTNsE?xxbLRKt~Fm-{rr*E6d&Vh$)gAsz{xaOFu;nGG%M80rBuyPMr ztudf?C9G#OP8YJmdhDKIoU)hm4siQ0^xR~8)v#$?V+6WCc2D^abi5g+dnN?eJ`*U& z6{!-AuZQC1xOyVP#9Js{KFJ&Si~!4#QzjXof~KCx;-UD%;m%7G?JSnx!KWn-u@br4 z|LTpk!0|cwlDiO{CsSxu3>@9*j3157zf%j;&1h>o=`V8ii!padIm zF*1yYh$&cv66EC)C8MKq>)Y9f;L;6liJ20n8UO_aBco%vBn5@iERS%x-edMc!r}-4 z(u9Cp8bg8eQ}j&H(9jwoygqaLfWW6giR7}rNgM@@=<8!G=P<1L{xdOmAAp{Qxe8g$ zZcNYx3=>&!C;1LD(^Lq))K!@N8FVlYR7zIj>t|O3oTe0nZ)+E|_&vZ&8Sqc%R0p1t zeG$If$@2CHk9)x+_crsY1J9Czi_OF4U}K3XI0!kQiWXD>URk191Wbf@4)f=RQ<4UX z@pjbYu4jP8RIoBB#(U_{VfZ!SI%*cSpsdp3Bz^Vc2D3~-Lx0!ja2yCf*H6&}?;kG= zohTLwu^<9cN|ET!6xnRkR|6ol1S-$|1fh9LbrDjR12v`HZN2v$PWuX$*~l>btCiKE z)Qc$y#`4CG11`4!_-*hd4c~XQE_mEojN*kk#lr%{0!ZNecN6?BC_oDIfvUAdPa(e?Og+YB5F&G79nF_8C7t zKs^s%D?*6SY&voUAdfx;LCp2RBiG}eeh@4@I1`w=5705Gicwo8)gi>ZHeSJapYdX_ z1X*uI9H|CD#j=$ST0wSGFKh-{JsDP)9yVhxb8BlIXap^KCX znswa-EhXI4xv(P$a{#PGt|4Z?o0lucoEYV@y=|qXR1}yli_jwRpWC(p(_i6B?%^4o z)&;E=9h5>9(UUhGi&zP&nF*>PXBh_2cBh^3N_0Bs|Crh)XMj?iK`A8mYlspWwvTjz zfFD{FlTP2z=)h*s$bI;l!&mvqmOZ?nj*XdzE9Of?!iuVpR$Jk)zQ1P9mgW$ zFHT-m2OJh56w#(DsLZ6GlP#YECo;fjqvQyFX3O+1t7ACv!U01rCS7yM+lSS73J=CI znCWqa3B<&}QCD*9%$q29ei~Q+R<>22Y? zHFX3>Y$j%r29xe)*Y`3jz~BHUew-q6c#EkGu&CnoDdz(0z@J(-)NF2x zzNwDIw0q^>$bX~v;ovx*-tT-Fu19$TyJWz9y{Aq>)zj}12b}_H@M1pbgU_Wabq+Va z!6^v1%}~|#C6v120To@ft_*drC>H1Ro;-6PC4H428;9h-$J|G^p5~bLQeWgg;Z}NV)`R;S$}Y9Q6X^G5CY4B;PO_@nlvrQSBCuKC2;18iHJrk z8dUE?H0bm>xS$V#SXzsUD3&iAOKj!HJ^RQL7L@(i2j(r1zo9|?SB_y^D13zx>?%Te zxN~OsW`QiRpeC}UI9FFUbvt$9l#v-j%q;amJt57a*)`2<0tls1+PX3k3nDRnT?r8FS@fG#t8I$hlr`I9y8ccBNIsc&fk_ zn}ij+%v!1~q`{wHIkEvJw|x~zSEv+bO)UFoDBiBSdk>tX zir3w&3N$DN1jSa0haC|7P^NfDdd11~kG>F3O<-lkiO}qL%U>j|wdv%MS*OXnfF3s7 zjw9$b?(~cF)J27TTZOdVVn=~QR1xyD(_bjO>{_E?cR}wXz^#T8%s6!UMP}sP4?6uh zDYkPM5}pWz$vir%+bN_5Y|me$j>L3ikIU8r2W0`Z zCP0~7T62STlV8bFv}pyJ_42HVWjye)Cs2otAmj^x4&Yy+1g}69z?4Q9A@?ZlSp!+{ ztee{NBB<9N4D3y?(7E3)vY_r+?DoZh=3UszGHhzk0r|;Mj6Xmtabgss43Q{V+Zahd zKai?kW6hnB4X}%-MY+hcTzkw!giH^g=gbs=bAv@3t1IDhSLmMSeI8X>h%K?A4T}MB{30t6F4yM)L4*M7Niv)Ygt%t zmCsO_(8zlC+-p}4f*;ifXtMTi7OWLoqCD3ou|+E0LLEnW4)qA@f;&pq;JoBS%siwz zgy>@Y6+UImeK7ELn39qcu@lfeq{RYkVlc^|A!>iiDz5|M^&|*@UPSbLm{e6V;7Ia0 zHf19Z4RWW2)9JrKN0-56$+Ex;ns2Hm36N0OoI(S|xcans;cSTGE)Z^{(I<$jN<(qf zOv@rZ3KMo)SWo~)j|5_5%iMsNsuCI|j1f(SI+EU)hg529+MK!rD6D~@-;uD&cv#l1 zn#xEYX00@k+zx)k*?~ar2565Q+$T-@HFC~efdJP6u)>hviox_gB-|`B?@%nb#szQ< za+=b3mR7=yG(($7U=DT<{?ytH#2RO)8Kk#oYo(wtkCuTNCwY0{Q3E5uM*grQM|Q+U zC2K`zPV(pq`rUc+QxRCEF^JHaaD}MlJ~#rD;W0 zANiO}qt3HRx2A*moR$o$3N~tmu2Rn@GQYwjvHmKafbhX5XqRN%eJ(;|t zMGrS?o46AcT?=BI%!jSEYei;SlvZ;g^C?Mn%pU$1u2aNDI88q2G@ej>o6r#pB8tW-1EFoZoskJrg820s{xR^3`ci z709@7LnmUH!@l#2M7HhXtXc)KyarhY5$-&4xCIw1X>LRzZr6z0+;2$8Uh$5wOWZr0x;z z*w{LTl^`2By6b`NqP!$Pt_2HOWR4CwMn$jIQC3`nYjP5IWU^*&78^?zgmdT}0cw${@)ASPTXhNvC=Xd5eNJL3#6(VuLG85fxf` zZTHMxvwSJ=s|Wm&(d>6sI|W5;p|xQ0ws3LdK$_?TF+om`x?k50Ua_wVQy8{OPcY{8 zrO(gF0p_ECc`~A9ceLYIsD~9DmA`WGRnIn{{MMj+QZ)zb#B13?!Z*`(;=!nwzhXS0^Yp@X3Fy-SuS;S|)uOhQz6qLKr3m41-aPI{CB!3U$B+WJFtjW2J!2w{TUC0LLk| z&RNt?;?+|&)(`~KAfm}4Y(ChXg?+q9&yySH28M-0>K1U+K)%YZbK|O1Jo@|!Sa?c? zb4MMC^kG=LmJVXhtep=tcq0?G16nzJ$uv=BpbJ$g0?WAgnlHQN2CC=542|4TdSF;p zX!&}dyc2|e3=pkQN9w-{FS@{XXlp_-Gn?w7gNk9x5S{O5#Gi!>nhr9MQz--RDDBo& z;L?z>VA{E%m}3jbkKs#d#;m1QWM`375C#(iDD%RQgpMVdrdQs7C;-P#G1M%~wzbkq zr_w*^Q8k#8T+;;9vJOm3dQC3;HH&IAqlyWQx(Tq$zuFqy{4a2Gav90mf)!eIU8B5h z9&h3G=SK*+(Qs^>Nd?nF+Y-m(ui!JoK>)IIF}X2))5@sTO4v zKvLqBnNx!xzlmWbgj|4o-TfCyF=fIy;C+0OZvq~Li*TgBZt7P>LX1uQ;Uz2I^`ToDf7z-500zf)MHwA6z zs4RyU8!XW(e?f!~Ci0$@t8Vnl3sW)2!Wnd-f5jE5bnklS`KVJLkOgOtWL!=e_AAtw zR8Xeau!O@iFpoFXjLVn25UY9<-}eZBE}2hHOsNJr z7ojwgJ!sJ>-s(HP=mvT z2#zA?TK}$_E+sF*EdYwDRem21?0U%qQA_D650N%SS?<6^0&qbtqMN5yfeU5YWDgJY zpDv4s?uSiK&RnQ#TnJI~;NmToyry=CH^?B6hQW4#@>XxS5?1~<5=S8=O(V(Ldnfo&KVd$yiYpG7QO>WH2bYg$)jKgZwfQ@{1#(z^t^Y zkWtfI#jo*-9RoT!lPf|897_j*&725wL7S>cjuep1SoM#&;>Ax(U@?;{5$a@AgI>Ir zy;Agv`E}Mi)7|hmoKDy6h76y8%RF_sp|8=v>s>&T(Aud{nJ(K|6_QG8G3|r`nWg$> zgMv7wFzq2{R|c@!qgx;)I&mVJPRhRUOkEQoBWbF+-mITh4N}zGGtk4WosOJ^%Pc4^ zH$A6%_)4QFg`>+e!~934EvSwUr2#p!?s$}yp0X9!1 zJ`|>TO`!$MM8yl9QL#Kv$6{ON!;PKL&!@Y?swc-6)MiScZNEZCt5{!US^=#-b*E=a z96;$c(%dGybDasN*cL%?SJpc)qDw z=keamoggARY{;8;Ywb-U!18+WV86f zr<-@6F05g$dE(Ten2C#Gv)m+zhFQDiL^|HwITQw+PJ|Bq-!j3}_8D;vk$!RrVapre zT`>hVgH8{fKCTcYXbCXJtkFaa!nRq^8SaJ$wxnKu% z`Q^Gr?gbFL1Y(@*IS7V>`-=CYR3@@Yu`A+RZf{x~p}*f47}$3Ouz3i+`^0w{sWwofYTg)DnHq>lg%`6Xb>pUn$_GN4L~F~ zS*zg{Wz97p}}8)xo7hP_Kg^bj`Hpz zWRo>hqZ*@L$DAqyRPcO`iP8-4Gh1ffg@P)_<3&rO=sb@9cgI|q6L5^6in1cmhTBrm zG1Y9!wEqYQgRUFb{3MIaymvTD`EOQGa23xB>*)q3Y6g+6;mR}r% zxJ(AobDT?j@|VCEQV$m8p_oy9vSki%wUWXx6)z1!5NNWcdJXb+%OECf0O)RP6HROA zIcq>E#&h|;C9O!nDDytwd2}FD9Z@h0foH2e*|OQgYjZq?IlFlIyL6E=*flyX+GjX! 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. + *

+ * 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 04/71] 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 05/71] 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 06/71] 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 07/71] 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 08/71] 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 09/71] 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 10/71] 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 11/71] 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 12/71] 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 13/71] 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 14/71] 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 15/71] 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 16/71] 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 17/71] 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 18/71] 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 19/71] 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 20/71] 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 21/71] 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 22/71] 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 23/71] 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 24/71] 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 25/71] 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 26/71] 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 27/71] 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 28/71] 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 29/71] 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 30/71] 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 31/71] 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 32/71] 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 33/71] 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 34/71] 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 35/71] 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 36/71] 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 37/71] 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 38/71] 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 39/71] 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 40/71] 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 41/71] 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 42/71] 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 43/71] 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 44/71] 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 45/71] 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 46/71] 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 47/71] 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 48/71] 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 49/71] 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 50/71] 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 51/71] 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 52/71] 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 53/71] 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 54/71] 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 55/71] 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 56/71] 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 57/71] 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 58/71] 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 59/71] 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 60/71] 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 61/71] 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 62/71] 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 63/71] 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 64/71] 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 65/71] 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 66/71] 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 67/71] 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 68/71] 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 69/71] 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); } From 1bc0252a82d9e63469780449f780e3fcf8586806 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Tue, 14 Jul 2026 11:14:35 -0700 Subject: [PATCH 70/71] Deprecate coupling to Jackson-2 API (#325) --- .../java/com/esri/core/geometry/GeometryEngine.java | 10 ++++++---- .../java/com/esri/core/geometry/JsonParserReader.java | 8 ++++---- .../java/com/esri/core/geometry/SpatialReference.java | 2 ++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 99466fd2..2ba61cf4 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -44,6 +44,7 @@ public class GeometryEngine { /** + * @deprecated Use jsonToGeometry(String) * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. * @@ -55,6 +56,7 @@ public class GeometryEngine { * @return The MapGeometry instance containing the imported geometry and its * spatial reference. */ + @Deprecated public static MapGeometry jsonToGeometry(JsonParser json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); return geom; @@ -95,11 +97,11 @@ public static MapGeometry jsonToGeometry(String json) { } /** - * Exports the specified geometry instance to it's JSON representation. + * Exports the specified geometry instance to its JSON representation. * * See OperatorExportToJson. * - * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, + * @see GeometryEngine#geometryToJson(SpatialReference spatialReference, * Geometry geometry) * @param wkid * The spatial reference Well Known ID to be used for the JSON @@ -114,7 +116,7 @@ public static String geometryToJson(int wkid, Geometry geometry) { } /** - * Exports the specified geometry instance to it's JSON representation. M + * Exports the specified geometry instance to its JSON representation. M * and Z values are not imported from JSON representation. * * See OperatorExportToJson. @@ -177,7 +179,7 @@ public static String geometryToGeoJson(int wkid, Geometry geometry) { } /** - * Exports the specified geometry instance to it's JSON representation. + * Exports the specified geometry instance to its JSON representation. * * See OperatorImportFromGeoJson. * diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index 90427c63..5313c0ab 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -27,9 +27,10 @@ import com.fasterxml.jackson.core.*; /** - * A throw in JsonReader built around the Jackson JsonParser. - * + * @deprecated Intended for internal use by geometry-api-java only. + * A throw-in JsonReader built around Jackson. */ +@Deprecated public class JsonParserReader implements JsonReader { private JsonParser m_jsonParser; @@ -46,7 +47,7 @@ public static JsonReader createFromString(String str) { try { JsonFactory factory = new JsonFactory(); JsonParser jsonParser = factory.createParser(str); - + jsonParser.nextToken(); return new JsonParserReader(jsonParser); } @@ -168,4 +169,3 @@ else if (t == Token.VALUE_FALSE) throw new JsonGeometryException("Not a boolean"); } } - diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 4c337e27..26e6c562 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -74,6 +74,7 @@ boolean isLocal() { } /** + * @deprecated Use fromJson(String) * Returns spatial reference from the JsonParser. * * @param parser @@ -83,6 +84,7 @@ boolean isLocal() { * @throws Exception * if parsing has failed */ + @Deprecated public static SpatialReference fromJson(JsonParser parser) throws Exception { return fromJson(new JsonParserReader(parser)); } From 539f032fd605b3bdfd025bdddb867b82a75f64a1 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 17 Jul 2026 19:39:00 -0700 Subject: [PATCH 71/71] Fix invalid check of envelope intersect for path envelopes --- .../com/esri/core/geometry/RelationalOperations.java | 2 +- .../java/com/esri/core/geometry/TestIntersect2.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index abf03372..34fdd216 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1609,7 +1609,7 @@ private static boolean polylineDisjointPolyline_(Polyline polyline_a, PairwiseIntersectorImpl intersector_paths = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); if (!intersector_paths.next()) - return false; + return true; return !linearPathIntersectsLinearPath_(polyline_a, polyline_b, tolerance); diff --git a/src/test/java/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java index 36860635..0254dcb8 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersect2.java +++ b/src/test/java/com/esri/core/geometry/TestIntersect2.java @@ -25,6 +25,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.Geometry.Type; +import com.esri.core.geometry.ogc.OGCGeometry; import junit.framework.TestCase; import org.junit.Test; @@ -39,6 +40,16 @@ protected void tearDown() throws Exception { super.tearDown(); } + @Test + public void testIntersectsLinestringAndMultilinestring() { + // https://github.com/Esri/geometry-api-java/issues/326 + OGCGeometry line = OGCGeometry.fromText("LINESTRING(1 0, 1 1)"); + OGCGeometry multiLine = OGCGeometry.fromText("MULTILINESTRING((0 0, 0 1), (2 0, 2 1))"); + + assertFalse(line.intersects(multiLine)); + assertFalse(multiLine.intersects(line)); + } + @Test /** * Intersect