csringhofer commented on code in PR #6581:
URL: https://github.com/apache/hive/pull/6581#discussion_r3652838558


##########
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeodesicLengthWGS84.java:
##########
@@ -66,37 +70,100 @@ public DoubleWritable evaluate(BytesWritable geomref) {
       return null;
     }
 
-    OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref);
-    if (ogcGeometry == null) {
+    Geometry geom = GeometryUtils.geometryFromEsriShape(geomref);
+    if (geom == null) {
       LogUtils.Log_ArgumentsNull(LOG);
       return null;
     }
 
-    Geometry esriGeom = ogcGeometry.getEsriGeometry();
-    switch (esriGeom.getType()) {
-    case Point:
-    case MultiPoint:
-      resultDouble.set(0.);
-      break;
-    default:
-      MultiPath lines = (MultiPath) (esriGeom);
-      int nPath = lines.getPathCount();
-      double length = 0.;
-      for (int ix = 0; ix < nPath; ix++) {
-        int curPt = lines.getPathStart(ix);
-        int pastPt = lines.getPathEnd(ix);
-        Point fromPt = lines.getPoint(curPt);
-        Point toPt = null;
-        for (int vx = curPt + 1; vx < pastPt; vx++) {
-          toPt = lines.getPoint(vx);
-          length += GeometryEngine.geodesicDistanceOnWGS84(fromPt, toPt);
-          fromPt = toPt;
+    switch (geom.getGeometryType()) {
+      case "Point", "MultiPoint" -> resultDouble.set(0.0);
+      case "LineString", "LinearRing" -> 
resultDouble.set(lineStringLength((LineString) geom));
+      case "MultiLineString" -> {
+        MultiLineString mls = (MultiLineString) geom;
+        double total = 0.0;
+        for (int i = 0; i < mls.getNumGeometries(); i++) {
+          total += lineStringLength((LineString) mls.getGeometryN(i));
         }
+        resultDouble.set(total);
       }
-      resultDouble.set(length);
-      break;
+      default -> resultDouble.set(0.0);
     }
 
     return resultDouble;
   }
+
+  /**
+   * Computes the geodesic length of a single LineString by summing the 
ellipsoidal
+   * (WGS84) distances between consecutive vertices.  Iterates the 
CoordinateSequence
+   * directly to avoid per-vertex Point object allocation.
+   */
+  private static double lineStringLength(LineString line) {
+    CoordinateSequence seq = line.getCoordinateSequence();
+    int nPts = seq.size();
+    if (nPts < 2) {
+      return 0.0;
+    }
+    double length = 0.0;
+    for (int i = 1; i < nPts; i++) {
+      length += vincentyDistanceMeters(
+          seq.getX(i - 1), seq.getY(i - 1),
+          seq.getX(i), seq.getY(i));
+    }
+    return length;
+  }
+
+  /**
+   * Ellipsoidal geodesic distance between two lon/lat points on the WGS84 
spheroid,
+   * using Vincenty's inverse formula (the same ellipsoidal model the ESRI 
library used,
+   * so results match the previous implementation).  Coordinates are in 
degrees, result

Review Comment:
   Wouldn't it be simpler to keep to ESRI lib for this functionality? 
https://github.com/Esri/geometry-api-java/blob/master/src/main/java/com/esri/core/geometry/GeoDist.java
 has its own Vincenty, or the same function could be used an in the original 
UDF.



##########
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriShapeConverter.java:
##########
@@ -0,0 +1,742 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hive.ql.udf.esri;
+
+import org.locationtech.jts.algorithm.Orientation;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateXYM;
+import org.locationtech.jts.geom.CoordinateXYZM;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads and writes raw ESRI shape binary format directly from/to JTS 
geometries,
+ * without any dependency on the ESRI Geometry library.
+ *
+ * <p>All binary values are little-endian as per the ESRI shape format 
specification
+ * (ESRI Shapefile Technical Description, July 1998,
+ * <a 
href="https://www.esri.com/content/dam/esrisites/sitecore-archive/Files/Pdfs/library/whitepapers/pdfs/shapefile.pdf";>
+ * shapefile.pdf</a>).
+ *
+ * <p>Supported shape types on read:
+ * <ul>
+ *   <li>0  - Null/Unknown</li>
+ *   <li>1  - Point</li>
+ *   <li>3  - Polyline (paths)</li>
+ *   <li>5  - Polygon (rings)</li>
+ *   <li>8  - MultiPoint</li>
+ *   <li>11 - PointZ (optionally with M)</li>
+ *   <li>13 - PolylineZ (optionally with M)</li>
+ *   <li>15 - PolygonZ (optionally with M)</li>
+ *   <li>18 - MultiPointZ (optionally with M)</li>
+ *   <li>21 - PointM</li>
+ *   <li>23 - PolylineM</li>
+ *   <li>25 - PolygonM</li>
+ *   <li>28 - MultiPointM</li>
+ * </ul>
+ *
+ * <p>The M-bearing types are read (to stay compatible with data serialized by 
the
+ * previous ESRI-library implementation) but never written; {@link 
#toEsriShape} emits
+ * only the XY and XYZ types.
+ */
+public final class EsriShapeConverter {
+
+  private static final int TYPE_NULL       = 0;
+  private static final int TYPE_POINT      = 1;
+  private static final int TYPE_POLYLINE   = 3;
+  private static final int TYPE_POLYGON    = 5;
+  private static final int TYPE_MULTIPOINT = 8;
+  private static final int TYPE_POINT_Z      = 11;
+  private static final int TYPE_POLYLINE_Z   = 13;
+  private static final int TYPE_POLYGON_Z    = 15;
+  private static final int TYPE_MULTIPOINT_Z = 18;
+  private static final int TYPE_POINT_M      = 21;
+  private static final int TYPE_POLYLINE_M   = 23;
+  private static final int TYPE_POLYGON_M    = 25;
+  private static final int TYPE_MULTIPOINT_M = 28;
+
+  // ESRI marks an absent measure with any value less than this sentinel.

Review Comment:
   The code uses <=  
   Which one is correct? Based on googling it seems that ==-1e38 is also 
considered no data.



##########
ql/src/test/queries/clientpositive/geospatial_udfs.q:
##########
@@ -1,3 +1,6 @@
+-- Truncate floating-point numbers to 12 decimal places for cross-platform 
reproducibility
+-- (JTS buffer uses Math.cos/sin which can differ by 1 ULP across JDK 
implementations)
+--! qt:replace:/(\d+\.\d{12})\d+/$1/
 -- create a table with two columns one for each point.

Review Comment:
   ack



##########
ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestEsriShapeConverter.java:
##########
@@ -0,0 +1,366 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hive.ql.udf.esri;
+
+import org.apache.hadoop.io.BytesWritable;
+import org.junit.Test;
+import org.locationtech.jts.algorithm.Orientation;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKTReader;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class TestEsriShapeConverter {
+
+  private static final double EPSILON = 1e-9;
+
+  private static final WKTReader WKT_READER = new 
WKTReader(GeometryUtils.GEOMETRY_FACTORY);
+
+  private static Geometry wkt(String text) throws ParseException {
+    return WKT_READER.read(text);
+  }
+
+  private static Geometry roundTrip(String text) throws ParseException {
+    Geometry geom = wkt(text);
+    byte[] shape = EsriShapeConverter.toEsriShape(geom);
+    Geometry back = 
EsriShapeConverter.fromEsriShapeBody(ByteBuffer.wrap(shape));
+    assertNotNull("Round-trip of " + text + " must not be null!", back);
+    return back;
+  }
+
+  /**
+   * Asserts coordinate-level equality after canonicalizing both geometries 
with
+   * {@link Geometry#normalize()}. Unlike {@code equalsTopo}, this catches 
ring reordering,
+   * ring/hole misassignment and orientation bugs (normalize fixes orientation 
and vertex
+   * order, so anything left is a real coordinate difference).
+   */
+  private static void assertEqualsNormalized(Geometry expected, Geometry 
actual) {
+    Geometry e = expected.copy();
+    Geometry a = actual.copy();
+    e.normalize();

Review Comment:
   Is normalization really needed? The round trips should not change the order 
of coordinates. Also, fixing orientation can actually hide bugs.
   
   I still think that equalsExact with tolerance is the right way to check 
round trips. The order of coordinates can matter, e.g. when representing a 
route with a linestring reversing it change semantics.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to