diff --git a/pom.xml b/pom.xml index 4481607165db..c1027bd72f79 100644 --- a/pom.xml +++ b/pom.xml @@ -137,6 +137,7 @@ 0.17.1 2.2.4 1.12.0 + 1.20.0 22.0 2.4.21 2.2.220 diff --git a/ql/pom.xml b/ql/pom.xml index 57672a607910..7e55637b495e 100644 --- a/ql/pom.xml +++ b/ql/pom.xml @@ -861,10 +861,16 @@ ${jersey.version} - com.esri.geometry - esri-geometry-api + org.locationtech.jts + jts-core + ${jts.version} + compile + + + org.locationtech.jts.io + jts-io-common + ${jts.version} compile - ${esri.version} org.roaringbitmap @@ -1104,10 +1110,10 @@ org.apache.datasketches:* org.apache.calcite:* org.apache.calcite.avatica:* - com.esri.geometry:esri-geometry-api org.roaringbitmap:RoaringBitmap com.github.luben:zstd-jni org.locationtech.jts:jts-core + org.locationtech.jts.io:jts-io-common org.apache.tez:tez-protobuf-history-plugin diff --git a/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeature.java b/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeature.java index e9316bd3dd9c..063492049f56 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeature.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeature.java @@ -17,7 +17,7 @@ */ package org.apache.hadoop.hive.ql.io.esriJson; -import com.esri.core.geometry.Geometry; +import org.locationtech.jts.geom.Geometry; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; @@ -34,7 +34,7 @@ public class EsriFeature { public Map attributes; /** - * Geometry associated with this feature + * JTS Geometry associated with this feature */ public Geometry geometry; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeatureClass.java b/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeatureClass.java index 1360c4d7d8b1..e4dbf9c6240a 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeatureClass.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeatureClass.java @@ -17,13 +17,17 @@ */ package org.apache.hadoop.hive.ql.io.esriJson; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.SpatialReference; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.apache.hadoop.hive.serde2.esriJson.deserializer.GeometryTypeJsonDeserializer; +import org.apache.hadoop.hive.serde2.esriJson.deserializer.SpatialReferenceJsonDeserializer; +import org.apache.hadoop.hive.serde2.esriJson.serializer.GeometryTypeJsonSerializer; +import org.apache.hadoop.hive.serde2.esriJson.serializer.SpatialReferenceJsonSerializer; import java.io.IOException; import java.io.InputStream; @@ -39,14 +43,19 @@ public class EsriFeatureClass { public Map fieldAliases; /** - * Esri geometry type (Polygon, Point, ...) + * Esri geometry type string (e.g. "esriGeometryPoint", "esriGeometryPolygon"). */ - public Geometry.Type geometryType; + @JsonDeserialize(using = GeometryTypeJsonDeserializer.class) + @JsonSerialize(using = GeometryTypeJsonSerializer.class) + public String geometryType; /** - * Spatial reference for the feature class (null, if undefined) + * Spatial reference WKID for the feature class (0 if undefined). + * The JSON form is {"wkid": N}; deserialized to a plain int. */ - public SpatialReference spatialReference; + @JsonDeserialize(using = SpatialReferenceJsonDeserializer.class) + @JsonSerialize(using = SpatialReferenceJsonSerializer.class) + public int spatialReference; /** * Array of field definitions (name, type, alias, ...) diff --git a/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriJsonFactory.java b/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriJsonFactory.java index 218b714be875..ea5689c933ca 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriJsonFactory.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriJsonFactory.java @@ -17,8 +17,6 @@ */ package org.apache.hadoop.hive.ql.io.esriJson; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.SpatialReference; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; @@ -29,11 +27,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.hadoop.hive.serde2.esriJson.deserializer.GeometryJsonDeserializer; -import org.apache.hadoop.hive.serde2.esriJson.deserializer.GeometryTypeJsonDeserializer; -import org.apache.hadoop.hive.serde2.esriJson.deserializer.SpatialReferenceJsonDeserializer; import org.apache.hadoop.hive.serde2.esriJson.serializer.GeometryJsonSerializer; -import org.apache.hadoop.hive.serde2.esriJson.serializer.GeometryTypeJsonSerializer; -import org.apache.hadoop.hive.serde2.esriJson.serializer.SpatialReferenceJsonSerializer; +import org.locationtech.jts.geom.Geometry; import java.io.IOException; import java.io.InputStream; @@ -48,14 +43,12 @@ public class EsriJsonFactory { SimpleModule module = new SimpleModule("EsriJsonModule", new Version(1, 0, 0, null)); - // add deserializers and serializers for types that can't be mapped field for field from the JSON + // Geometry: JTS Geometry <-> Esri JSON, handled module-wide because Geometry + // is a well-defined type with no ambiguity. + // geometryType (String) and spatialReference (int) use field-level @JsonDeserialize + // annotations on EsriFeatureClass to avoid intercepting all Strings/ints. module.addDeserializer(Geometry.class, new GeometryJsonDeserializer()); - module.addDeserializer(SpatialReference.class, new SpatialReferenceJsonDeserializer()); - module.addDeserializer(Geometry.Type.class, new GeometryTypeJsonDeserializer()); - module.addSerializer(Geometry.class, new GeometryJsonSerializer()); - module.addSerializer(Geometry.Type.class, new GeometryTypeJsonSerializer()); - module.addSerializer(SpatialReference.class, new SpatialReferenceJsonSerializer()); jsonObjectMapper.registerModule(module); } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/BinUtils.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/BinUtils.java index 9ed75a358b43..348b1613d03c 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/BinUtils.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/BinUtils.java @@ -17,7 +17,7 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; +import org.locationtech.jts.geom.Envelope; public class BinUtils { final long numCols; @@ -58,9 +58,9 @@ public long getId(double x, double y) { * Gets the envelope for the bin ID. * * @param binId - * @param envelope + * @return envelope */ - public void queryEnvelope(long binId, Envelope envelope) { + public Envelope queryEnvelope(long binId) { long down = binId / numCols; long over = binId % numCols; @@ -69,7 +69,7 @@ public void queryEnvelope(long binId, Envelope envelope) { double ymax = extentMax - (down * binSize); double ymin = ymax - binSize; - envelope.setCoords(xmin, ymin, xmax, ymax); + return new Envelope(xmin, xmax, ymin, ymax); } /** @@ -77,17 +77,17 @@ public void queryEnvelope(long binId, Envelope envelope) { * * @param x * @param y - * @param envelope + * @return envelope */ - public void queryEnvelope(double x, double y, Envelope envelope) { + public Envelope queryEnvelope(double x, double y) { double down = (extentMax - y) / binSize; double over = (x - extentMin) / binSize; - double xmin = extentMin + (over * binSize); + double xmin = extentMin + ((long) over * binSize); double xmax = xmin + binSize; - double ymax = extentMax - (down * binSize); + double ymax = extentMax - ((long) down * binSize); double ymin = ymax - binSize; - envelope.setCoords(xmin, ymin, xmax, ymax); + return new Envelope(xmin, xmax, ymin, ymax); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriJsonConverter.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriJsonConverter.java new file mode 100644 index 000000000000..02d69e73942a --- /dev/null +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriJsonConverter.java @@ -0,0 +1,482 @@ +/* + * 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 com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.locationtech.jts.algorithm.Orientation; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +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.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Converts between JTS geometries and Esri JSON format without any dependency + * on the ESRI geometry library. + * + *

Esri JSON format reference: + *

    + *
  • Point: {@code {"x":N,"y":N,"spatialReference":{"wkid":N}}}
  • + *
  • MultiPoint: {@code {"points":[[x,y],...],"spatialReference":{"wkid":N}}}
  • + *
  • LineString: {@code {"paths":[[[x,y],...]],"spatialReference":{"wkid":N}}}
  • + *
  • MultiLineString: {@code {"paths":[[[x,y],...],...],"spatialReference":{"wkid":N}}}
  • + *
  • Polygon: {@code {"rings":[[[x,y],...,[x,y]]],"spatialReference":{"wkid":N}}}
  • + *
  • MultiPolygon: {@code {"rings":[[[x,y],...],...],"spatialReference":{"wkid":N}}}
  • + *
+ * + *

Winding-order convention: Esri requires exterior rings to be clockwise (CW) and + * interior rings (holes) to be counter-clockwise (CCW) when viewed in screen coordinates + * (y-axis pointing down). JTS follows the OGC convention, which is the opposite. + * This converter checks and reverses rings as necessary using + * {@link Orientation#isCCW(Coordinate[])}. + */ +public final class EsriJsonConverter { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private EsriJsonConverter() { + } + + /** + * Converts a JTS {@link Geometry} to its Esri JSON string representation. + * + * @param geom the JTS geometry to convert; must not be {@code null} + * @param wkid the Well-Known ID of the spatial reference (0 = omit spatialReference) + * @return Esri JSON string + * @throws IllegalArgumentException if the geometry type is not supported + */ + public static String geometryToEsriJson(Geometry geom, int wkid) { + if (geom == null) { + throw new IllegalArgumentException("geom must not be null"); + } + + StringBuilder sb = new StringBuilder(); + + if (geom instanceof Point point) { + writePoint(point, wkid, sb); + } else if (geom instanceof MultiPoint multiPoint) { + writeMultiPoint(multiPoint, wkid, sb); + } else if (geom instanceof LineString) { + // Single path wrapped in the "paths" array + sb.append("{\"paths\":["); + writeCoordArray(geom.getCoordinates(), sb); + sb.append(']'); + appendSpatialReference(wkid, sb); + sb.append('}'); + } else if (geom instanceof MultiLineString multiLineString) { + writeMultiLineString(multiLineString, wkid, sb); + } else if (geom instanceof Polygon polygon) { + sb.append("{\"rings\":["); + writePolygonRings(polygon, sb); + sb.append(']'); + appendSpatialReference(wkid, sb); + sb.append('}'); + } else if (geom instanceof MultiPolygon multiPolygon) { + writeMultiPolygon(multiPolygon, wkid, sb); + } else { + throw new IllegalArgumentException("Unsupported geometry type: " + geom.getGeometryType()); + } + + return sb.toString(); + } + + private static void writePoint(Point pt, int wkid, StringBuilder sb) { + sb.append("{\"x\":"); + appendDouble(pt.getX(), sb); + sb.append(",\"y\":"); + appendDouble(pt.getY(), sb); + double z = pt.getCoordinate().getZ(); + if (!Double.isNaN(z)) { + sb.append(",\"z\":"); + appendDouble(z, sb); + } + appendSpatialReference(wkid, sb); + sb.append('}'); + } + + private static void writeMultiPoint(MultiPoint mp, int wkid, StringBuilder sb) { + sb.append("{\"points\":["); + int n = mp.getNumGeometries(); + for (int i = 0; i < n; i++) { + if (i > 0) { + sb.append(','); + } + writeCoord(mp.getGeometryN(i).getCoordinate(), sb); + } + sb.append(']'); + appendSpatialReference(wkid, sb); + sb.append('}'); + } + + private static void writeMultiLineString(MultiLineString mls, int wkid, StringBuilder sb) { + sb.append("{\"paths\":["); + int n = mls.getNumGeometries(); + for (int i = 0; i < n; i++) { + if (i > 0) { + sb.append(','); + } + writeCoordArray(mls.getGeometryN(i).getCoordinates(), sb); + } + sb.append(']'); + appendSpatialReference(wkid, sb); + sb.append('}'); + } + + /** + * Writes the rings for a single {@link Polygon} into {@code sb} (without the outer + * braces or the spatialReference). The caller is responsible for opening/closing + * the "rings" key. + * + *

Esri winding order: exterior ring CW, interior rings CCW. + */ + private static void writePolygonRings(Polygon poly, StringBuilder sb) { + // Exterior ring: must be CW in Esri convention. + LineString exterior = poly.getExteriorRing(); + Coordinate[] extCoords = ensureCW(exterior.getCoordinates()); + writeCoordArray(extCoords, sb); + + // Interior rings (holes): must be CCW in Esri convention. + for (int h = 0; h < poly.getNumInteriorRing(); h++) { + sb.append(','); + Coordinate[] holeCoords = ensureCCW(poly.getInteriorRingN(h).getCoordinates()); + writeCoordArray(holeCoords, sb); + } + } + + private static void writeMultiPolygon(MultiPolygon mp, int wkid, StringBuilder sb) { + sb.append("{\"rings\":["); + boolean firstRing = true; + int n = mp.getNumGeometries(); + for (int i = 0; i < n; i++) { + Polygon poly = (Polygon) mp.getGeometryN(i); + + if (!firstRing) { + sb.append(','); + } + firstRing = false; + // Exterior ring + Coordinate[] extCoords = ensureCW(poly.getExteriorRing().getCoordinates()); + writeCoordArray(extCoords, sb); + + // Interior rings + for (int h = 0; h < poly.getNumInteriorRing(); h++) { + sb.append(','); + Coordinate[] holeCoords = ensureCCW(poly.getInteriorRingN(h).getCoordinates()); + writeCoordArray(holeCoords, sb); + } + } + sb.append(']'); + appendSpatialReference(wkid, sb); + sb.append('}'); + } + + /** Writes a coordinate sequence as a JSON array: {@code [[x,y],[x,y],...]}. */ + private static void writeCoordArray(Coordinate[] coords, StringBuilder sb) { + sb.append('['); + for (int i = 0; i < coords.length; i++) { + if (i > 0) { + sb.append(','); + } + writeCoord(coords[i], sb); + } + sb.append(']'); + } + + /** Writes a single coordinate as {@code [x,y]} or {@code [x,y,z]} if Z is present. */ + private static void writeCoord(Coordinate c, StringBuilder sb) { + sb.append('['); + appendDouble(c.x, sb); + sb.append(','); + appendDouble(c.y, sb); + if (!Double.isNaN(c.getZ())) { + sb.append(','); + appendDouble(c.getZ(), sb); + } + sb.append(']'); + } + + /** Appends {@code ,"spatialReference":{"wkid":N}} unless {@code wkid == 0}. */ + private static void appendSpatialReference(int wkid, StringBuilder sb) { + if (wkid != 0) { + sb.append(",\"spatialReference\":{\"wkid\":"); + sb.append(wkid); + sb.append('}'); + } + } + + /** + * Appends a double value, formatting whole numbers without a decimal point + * (e.g. 2 not 2.0) to match Esri JSON conventions. + */ + private static void appendDouble(double v, StringBuilder sb) { + long lv = (long) v; + if (v == lv) { + sb.append(lv); + } else { + sb.append(Double.toString(v)); + } + } + + /** + * Returns the coordinate array reversed in-place if it is currently CCW + * (i.e. makes it CW for Esri exterior rings). + */ + private static Coordinate[] ensureCW(Coordinate[] coords) { + if (Orientation.isCCW(coords)) { + return reverse(coords); + } + return coords; + } + + /** + * Returns the coordinate array reversed if it is currently CW + * (i.e. makes it CCW for Esri interior/hole rings). + */ + private static Coordinate[] ensureCCW(Coordinate[] coords) { + if (!Orientation.isCCW(coords)) { + return reverse(coords); + } + return coords; + } + + /** Returns a new array that is the reverse of {@code coords}. */ + private static Coordinate[] reverse(Coordinate[] coords) { + Coordinate[] copy = Arrays.copyOf(coords, coords.length); + for (int i = 0, j = copy.length - 1; i < j; i++, j--) { + Coordinate tmp = copy[i]; + copy[i] = copy[j]; + copy[j] = tmp; + } + return copy; + } + + /** + * Parses an Esri JSON string into a JTS {@link Geometry}. + * + *

Supported Esri JSON geometry types: + *

    + *
  • {@code {"x":N,"y":N}} → {@link Point}
  • + *
  • {@code {"points":[[x,y],...]}} → {@link MultiPoint}
  • + *
  • {@code {"paths":[[[x,y],...]...]}} → {@link LineString} (single path) or + * {@link MultiLineString} (multiple paths)
  • + *
  • {@code {"rings":[[[x,y],...]...]}} → {@link Polygon} or {@link MultiPolygon}
  • + *
+ * + *

If a {@code "spatialReference":{"wkid":N}} object is present the SRID is set on + * the returned geometry. + * + * @param json the Esri JSON string; must not be {@code null} + * @return the parsed JTS geometry, or {@code null} if the string cannot be parsed + */ + public static Geometry esriJsonToGeometry(String json) { + if (json == null || json.isEmpty()) { + return null; + } + + JsonNode root; + try { + root = OBJECT_MAPPER.readTree(json); + } catch (IOException e) { + return null; + } + + if (!root.isObject()) { + return null; + } + + int wkid = parseSrid(root); + Geometry geom; + + if (root.has("x")) { + geom = parsePoint(root); + } else if (root.has("points")) { + geom = parseMultiPoint(root); + } else if (root.has("paths")) { + geom = parsePaths(root); + } else if (root.has("rings")) { + geom = parseRings(root); + } else { + return null; + } + + if (geom != null && wkid != 0) { + geom.setSRID(wkid); + } + return geom; + } + + private static int parseSrid(JsonNode root) { + JsonNode sr = root.get("spatialReference"); + if (sr != null && sr.has("wkid")) { + return sr.get("wkid").asInt(0); + } + return 0; + } + + private static Point parsePoint(JsonNode root) { + double x = root.get("x").asDouble(); + double y = root.get("y").asDouble(); + Coordinate coord; + if (root.has("z")) { + coord = new Coordinate(x, y, root.get("z").asDouble()); + } else { + coord = new Coordinate(x, y); + } + return GeometryUtils.GEOMETRY_FACTORY.createPoint(coord); + } + + private static MultiPoint parseMultiPoint(JsonNode root) { + JsonNode pts = root.get("points"); + if (!pts.isArray()) { + return null; + } + Coordinate[] coords = readCoordArray(pts); + return GeometryUtils.GEOMETRY_FACTORY.createMultiPointFromCoords(coords); + } + + private static Geometry parsePaths(JsonNode root) { + JsonNode paths = root.get("paths"); + if (!paths.isArray() || paths.isEmpty()) { + return null; + } + LineString[] lines = new LineString[paths.size()]; + for (int i = 0; i < paths.size(); i++) { + Coordinate[] coords = readCoordArray(paths.get(i)); + lines[i] = GeometryUtils.GEOMETRY_FACTORY.createLineString(coords); + } + if (lines.length == 1) { + return lines[0]; + } + return GeometryUtils.GEOMETRY_FACTORY.createMultiLineString(lines); + } + + /** + * Parses an Esri JSON "rings" array into a {@link Polygon} or {@link MultiPolygon}. + * + *

Strategy: iterate rings; a ring is an exterior ring if it is CW (positive signed + * area using the Shoelace formula in screen-coordinate convention, which is what Esri + * uses). Each exterior ring starts a new polygon; CCW rings that follow are holes of + * the preceding exterior ring. + * + *

In practice we use {@link Orientation#isCCW} to classify: a ring that is not + * CCW (i.e. is CW) is treated as an exterior ring. + */ + private static Geometry parseRings(JsonNode root) { + JsonNode rings = root.get("rings"); + if (!rings.isArray() || rings.isEmpty()) { + return null; + } + + // Build list of (coords, isExterior) pairs + List exteriors = new ArrayList<>(); + // holes[i] is the list of hole coordinate arrays for exteriors.get(i) + List> holes = new ArrayList<>(); + + for (int i = 0; i < rings.size(); i++) { + Coordinate[] coords = readCoordArray(rings.get(i)); + // Ensure the ring is closed + coords = ensureClosed(coords); + + // In Esri JSON: exterior rings are CW, interior (holes) are CCW. + // Orientation.isCCW returns true for CCW rings. + boolean isCCW = Orientation.isCCW(coords); + if (!isCCW) { + // CW → exterior ring + exteriors.add(coords); + holes.add(new ArrayList<>()); + } else { + // CCW → hole belonging to the most recent exterior ring + if (exteriors.isEmpty()) { + // Defensive: treat as exterior if no preceding exterior ring found + exteriors.add(coords); + holes.add(new ArrayList<>()); + } else { + holes.get(holes.size() - 1).add(coords); + } + } + } + + if (exteriors.isEmpty()) { + return null; + } + + Polygon[] polygons = new Polygon[exteriors.size()]; + for (int i = 0; i < exteriors.size(); i++) { + LinearRing shell = GeometryUtils.GEOMETRY_FACTORY.createLinearRing(exteriors.get(i)); + List holeList = holes.get(i); + LinearRing[] holeRings = new LinearRing[holeList.size()]; + for (int h = 0; h < holeList.size(); h++) { + holeRings[h] = GeometryUtils.GEOMETRY_FACTORY.createLinearRing(holeList.get(h)); + } + polygons[i] = GeometryUtils.GEOMETRY_FACTORY.createPolygon(shell, holeRings); + } + + if (polygons.length == 1) { + return polygons[0]; + } + return GeometryUtils.GEOMETRY_FACTORY.createMultiPolygon(polygons); + } + + /** + * Reads a JSON array-of-arrays into a {@link Coordinate} array. + * Each element must be {@code [x, y]} or {@code [x, y, z]}. + */ + private static Coordinate[] readCoordArray(JsonNode array) { + Coordinate[] coords = new Coordinate[array.size()]; + for (int i = 0; i < array.size(); i++) { + JsonNode pt = array.get(i); + double x = pt.get(0).asDouble(); + double y = pt.get(1).asDouble(); + if (pt.size() >= 3) { + coords[i] = new Coordinate(x, y, pt.get(2).asDouble()); + } else { + coords[i] = new Coordinate(x, y); + } + } + return coords; + } + + /** + * Returns a coordinate array that is guaranteed to be closed (first == last). + * If the input is already closed it is returned unchanged; otherwise a new array + * with the first coordinate appended at the end is returned. + */ + private static Coordinate[] ensureClosed(Coordinate[] coords) { + if (coords.length == 0) { + return coords; + } + Coordinate first = coords[0]; + Coordinate last = coords[coords.length - 1]; + if (first.equals3D(last)) { + return coords; + } + Coordinate[] closed = Arrays.copyOf(coords, coords.length + 1); + closed[coords.length] = first.copy(); + return closed; + } +} diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriShapeConverter.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriShapeConverter.java new file mode 100644 index 000000000000..a5eaf7c59a74 --- /dev/null +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriShapeConverter.java @@ -0,0 +1,588 @@ +/* + * 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.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. + * + *

All binary values are little-endian as per the ESRI shape format specification. + * + *

Supported shape types: + *

    + *
  • 0 - Null/Unknown
  • + *
  • 1 - Point
  • + *
  • 3 - Polyline (paths)
  • + *
  • 5 - Polygon (rings)
  • + *
  • 8 - MultiPoint
  • + *
  • 11 - PointZ
  • + *
  • 13 - PolylineZ
  • + *
  • 15 - PolygonZ
  • + *
  • 18 - MultiPointZ
  • + *
+ */ +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 EsriShapeConverter() { + } + + /** + * Reads the ESRI shape binary from {@code shapeBuffer} (already positioned at start, + * little-endian byte order will be set internally) and returns a JTS {@link Geometry}. + * + * @param shapeBuffer buffer containing raw ESRI shape bytes starting at its current position + * @return a JTS Geometry, or {@code null} for the Null/Unknown shape type + * @throws IllegalArgumentException if the buffer contains an unsupported or malformed shape + */ + public static Geometry fromEsriShape(ByteBuffer shapeBuffer) { + shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); + int shapeType = shapeBuffer.getInt(); + + return switch (shapeType) { + case TYPE_NULL -> null; + case TYPE_POINT -> readPoint(shapeBuffer, false); + case TYPE_POINT_Z -> readPoint(shapeBuffer, true); + case TYPE_MULTIPOINT -> readMultiPoint(shapeBuffer, false); + case TYPE_MULTIPOINT_Z -> readMultiPoint(shapeBuffer, true); + case TYPE_POLYLINE -> readPolyline(shapeBuffer, false); + case TYPE_POLYLINE_Z -> readPolyline(shapeBuffer, true); + case TYPE_POLYGON -> readPolygon(shapeBuffer, false); + case TYPE_POLYGON_Z -> readPolygon(shapeBuffer, true); + default -> throw new IllegalArgumentException("Unsupported ESRI shape type: " + shapeType); + }; + } + + private static Point readPoint(ByteBuffer buf, boolean hasZ) { + double x = buf.getDouble(); + double y = buf.getDouble(); + if (hasZ) { + double z = buf.getDouble(); + return GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(x, y, z)); + } + return GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(x, y)); + } + + private static MultiPoint readMultiPoint(ByteBuffer buf, boolean hasZ) { + // skip bounding box (4 doubles = 32 bytes) + buf.position(buf.position() + 32); + int numPoints = buf.getInt(); + double[] xs = new double[numPoints]; + double[] ys = new double[numPoints]; + for (int i = 0; i < numPoints; i++) { + xs[i] = buf.getDouble(); + ys[i] = buf.getDouble(); + } + double[] zs = null; + if (hasZ) { + // skip zMin, zMax + buf.position(buf.position() + 16); + zs = new double[numPoints]; + for (int i = 0; i < numPoints; i++) { + zs[i] = buf.getDouble(); + } + } + Point[] points = new Point[numPoints]; + for (int i = 0; i < numPoints; i++) { + Coordinate coord = (zs != null) ? new Coordinate(xs[i], ys[i], zs[i]) + : new Coordinate(xs[i], ys[i]); + points[i] = GeometryUtils.GEOMETRY_FACTORY.createPoint(coord); + } + return GeometryUtils.GEOMETRY_FACTORY.createMultiPoint(points); + } + + /** + * Read a Polyline or PolylineZ. Buffer is positioned just after the type int. + * Multiple parts become a MultiLineString; a single part becomes a LineString. + */ + private static Geometry readPolyline(ByteBuffer buf, boolean hasZ) { + // skip bounding box + buf.position(buf.position() + 32); + int numParts = buf.getInt(); + int numPoints = buf.getInt(); + + int[] partStarts = new int[numParts]; + for (int i = 0; i < numParts; i++) { + partStarts[i] = buf.getInt(); + } + + Coordinate[] allCoords = readXYCoords(buf, numPoints); + if (hasZ) { + readZIntoCoords(buf, allCoords); + } + + LineString[] lines = new LineString[numParts]; + for (int i = 0; i < numParts; i++) { + int start = partStarts[i]; + int end = (i + 1 < numParts) ? partStarts[i + 1] : numPoints; + Coordinate[] partCoords = copyRange(allCoords, start, end); + lines[i] = GeometryUtils.GEOMETRY_FACTORY.createLineString(partCoords); + } + + if (lines.length == 1) { + return lines[0]; + } + return GeometryUtils.GEOMETRY_FACTORY.createMultiLineString(lines); + } + + /** + * Read a Polygon or PolygonZ. Buffer is positioned just after the type int. + * + *

In ESRI shape format, exterior rings are wound clockwise (negative signed area) + * and holes are wound counterclockwise (positive signed area). Multiple exterior rings + * produce a MultiPolygon. + */ + private static Geometry readPolygon(ByteBuffer buf, boolean hasZ) { + // skip bounding box + buf.position(buf.position() + 32); + int numParts = buf.getInt(); + int numPoints = buf.getInt(); + + int[] partStarts = new int[numParts]; + for (int i = 0; i < numParts; i++) { + partStarts[i] = buf.getInt(); + } + + Coordinate[] allCoords = readXYCoords(buf, numPoints); + if (hasZ) { + readZIntoCoords(buf, allCoords); + } + + // Split into per-ring coordinate arrays + Coordinate[][] rings = new Coordinate[numParts][]; + for (int i = 0; i < numParts; i++) { + int start = partStarts[i]; + int end = (i + 1 < numParts) ? partStarts[i + 1] : numPoints; + rings[i] = copyRange(allCoords, start, end); + } + + // Classify rings: clockwise = exterior (ESRI convention); CCW = hole + // Build list of (exterior ring index, list of hole ring indices) + List exteriorIndices = new ArrayList<>(); + List> holesByExterior = new ArrayList<>(); + + for (int i = 0; i < rings.length; i++) { + if (!Orientation.isCCW(rings[i])) { + // clockwise -> exterior + exteriorIndices.add(i); + holesByExterior.add(new ArrayList<>()); + } + } + + // Assign holes to the nearest preceding exterior ring + int exteriorCount = exteriorIndices.size(); + for (int i = 0; i < rings.length; i++) { + if (Orientation.isCCW(rings[i])) { + // counterclockwise -> hole; assign to the last exterior ring that precedes it + int ownerSlot = exteriorCount - 1; + for (int e = 0; e < exteriorCount; e++) { + if (exteriorIndices.get(e) > i) { + ownerSlot = e - 1; + break; + } + } + if (ownerSlot < 0) { + ownerSlot = 0; + } + holesByExterior.get(ownerSlot).add(i); + } + } + + // Build JTS Polygons + Polygon[] polygons = new Polygon[exteriorCount]; + for (int e = 0; e < exteriorCount; e++) { + LinearRing shell = GeometryUtils.GEOMETRY_FACTORY.createLinearRing(rings[exteriorIndices.get(e)]); + List holeIndices = holesByExterior.get(e); + LinearRing[] holes = new LinearRing[holeIndices.size()]; + for (int h = 0; h < holes.length; h++) { + holes[h] = GeometryUtils.GEOMETRY_FACTORY.createLinearRing(rings[holeIndices.get(h)]); + } + polygons[e] = GeometryUtils.GEOMETRY_FACTORY.createPolygon(shell, holes); + } + + if (polygons.length == 1) { + return polygons[0]; + } + return GeometryUtils.GEOMETRY_FACTORY.createMultiPolygon(polygons); + } + + /** + * Writes the JTS {@link Geometry} to ESRI shape binary format and returns the bytes. + * All values are little-endian. + * + * @param geom the JTS geometry to serialize; if {@code null} a 4-byte null shape is returned + * @return raw ESRI shape bytes + * @throws IllegalArgumentException if the geometry type is not supported + */ + public static byte[] toEsriShape(Geometry geom) { + if (geom == null || geom.isEmpty()) { + ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(TYPE_NULL); + return buf.array(); + } + + return switch (geom.getGeometryType()) { + case "Point" -> writePoint((Point) geom); + case "MultiPoint" -> writeMultiPoint((MultiPoint) geom); + case "LineString", "LinearRing" -> writeLineString((LineString) geom); + case "MultiLineString" -> writeMultiLineString((MultiLineString) geom); + case "Polygon" -> writePolygonBuffer(collectPolygonRings((Polygon) geom)); + case "MultiPolygon" -> writeMultiPolygonShape((MultiPolygon) geom); + case "GeometryCollection" -> writeGeometryCollection((GeometryCollection) geom); + default -> throw new IllegalArgumentException("Unsupported geometry type: " + geom.getGeometryType()); + }; + } + + private static byte[] writeLineString(LineString ls) { + Coordinate[] coords = ls.getCoordinates(); + Coordinate[][] parts = {coords}; + return writePolylineBuffer(parts, coordsHaveZ(coords)); + } + + private static byte[] writeMultiLineString(MultiLineString mls) { + Coordinate[][] parts = new Coordinate[mls.getNumGeometries()][]; + boolean hasZ = false; + for (int i = 0; i < parts.length; i++) { + parts[i] = mls.getGeometryN(i).getCoordinates(); + if (!hasZ && coordsHaveZ(parts[i])) { + hasZ = true; + } + } + return writePolylineBuffer(parts, hasZ); + } + + private static byte[] writeMultiPolygonShape(MultiPolygon mp) { + List allRings = new ArrayList<>(); + for (int i = 0; i < mp.getNumGeometries(); i++) { + allRings.addAll(collectPolygonRings((Polygon) mp.getGeometryN(i))); + } + return writePolygonBuffer(allRings); + } + + private static byte[] writeGeometryCollection(GeometryCollection gc) { + if (gc.getNumGeometries() == 0) { + ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(TYPE_NULL); + return buf.array(); + } + throw new IllegalArgumentException( + "Heterogeneous GeometryCollection is not supported by ESRI shape format"); + } + + private static byte[] writePoint(Point point) { + Coordinate c = point.getCoordinate(); + boolean hasZ = !Double.isNaN(c.getZ()); + if (hasZ) { + ByteBuffer buf = ByteBuffer.allocate(4 + 8 + 8 + 8).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(TYPE_POINT_Z); + buf.putDouble(c.getX()); + buf.putDouble(c.getY()); + buf.putDouble(c.getZ()); + return buf.array(); + } else { + ByteBuffer buf = ByteBuffer.allocate(4 + 8 + 8).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(TYPE_POINT); + buf.putDouble(c.getX()); + buf.putDouble(c.getY()); + return buf.array(); + } + } + + private static byte[] writeMultiPoint(MultiPoint mp) { + int n = mp.getNumGeometries(); + Coordinate[] coords = new Coordinate[n]; + for (int i = 0; i < n; i++) { + coords[i] = mp.getGeometryN(i).getCoordinate(); + } + boolean hasZ = coordsHaveZ(coords); + + double[] bbox = bbox2D(coords); + // type(4) + bbox(32) + numPoints(4) + points*16 [+ zMin+zMax(16) + n*8 if Z] + int baseSize = 4 + 32 + 4 + n * 16; + int size = hasZ ? baseSize + 16 + n * 8 : baseSize; + int shapeType = hasZ ? TYPE_MULTIPOINT_Z : TYPE_MULTIPOINT; + + ByteBuffer buf = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(shapeType); + writeBbox(buf, bbox); + buf.putInt(n); + for (Coordinate c : coords) { + buf.putDouble(c.getX()); + buf.putDouble(c.getY()); + } + if (hasZ) { + writeZSection(buf, coords); + } + return buf.array(); + } + + /** + * Write Polyline (type 3 or 13) from an array of coordinate rings/paths. + */ + private static byte[] writePolylineBuffer(Coordinate[][] parts, boolean hasZ) { + int numParts = parts.length; + int numPoints = 0; + for (Coordinate[] part : parts) { + numPoints += part.length; + } + + Coordinate[] allCoords = flattenParts(parts); + double[] bbox = bbox2D(allCoords); + int shapeType = hasZ ? TYPE_POLYLINE_Z : TYPE_POLYLINE; + + // type(4) + bbox(32) + numParts(4) + numPoints(4) + parts*4 + points*16 [+ Z section] + int baseSize = 4 + 32 + 4 + 4 + numParts * 4 + numPoints * 16; + int size = hasZ ? baseSize + 16 + numPoints * 8 : baseSize; + + ByteBuffer buf = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(shapeType); + writeBbox(buf, bbox); + buf.putInt(numParts); + buf.putInt(numPoints); + + int offset = 0; + for (Coordinate[] part : parts) { + buf.putInt(offset); + offset += part.length; + } + for (Coordinate c : allCoords) { + buf.putDouble(c.getX()); + buf.putDouble(c.getY()); + } + if (hasZ) { + writeZSection(buf, allCoords); + } + return buf.array(); + } + + /** + * Collect all rings from a Polygon as Coordinate arrays, ensuring exterior rings are + * clockwise and holes are counterclockwise (ESRI convention). + */ + private static List collectPolygonRings(Polygon polygon) { + List rings = new ArrayList<>(); + Coordinate[] shellCoords = polygon.getExteriorRing().getCoordinates(); + // Exterior ring must be clockwise (ESRI convention) + if (Orientation.isCCW(shellCoords)) { + shellCoords = reverse(shellCoords); + } + rings.add(shellCoords); + + for (int h = 0; h < polygon.getNumInteriorRing(); h++) { + Coordinate[] holeCoords = polygon.getInteriorRingN(h).getCoordinates(); + // Hole must be counterclockwise (ESRI convention) + if (!Orientation.isCCW(holeCoords)) { + holeCoords = reverse(holeCoords); + } + rings.add(holeCoords); + } + return rings; + } + + /** + * Write Polygon (type 5 or 15) from a list of ring coordinate arrays. + * The first ring in each polygon group must be the exterior (clockwise), followed by + * its holes (counterclockwise). This method accepts already-oriented rings. + */ + private static byte[] writePolygonBuffer(List rings) { + int numParts = rings.size(); + int numPoints = 0; + for (Coordinate[] ring : rings) { + numPoints += ring.length; + } + + Coordinate[] allCoords = new Coordinate[numPoints]; + int pos = 0; + for (Coordinate[] ring : rings) { + System.arraycopy(ring, 0, allCoords, pos, ring.length); + pos += ring.length; + } + + boolean hasZ = coordsHaveZ(allCoords); + double[] bbox = bbox2D(allCoords); + int shapeType = hasZ ? TYPE_POLYGON_Z : TYPE_POLYGON; + + int baseSize = 4 + 32 + 4 + 4 + numParts * 4 + numPoints * 16; + int size = hasZ ? baseSize + 16 + numPoints * 8 : baseSize; + + ByteBuffer buf = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(shapeType); + writeBbox(buf, bbox); + buf.putInt(numParts); + buf.putInt(numPoints); + + int offset = 0; + for (Coordinate[] ring : rings) { + buf.putInt(offset); + offset += ring.length; + } + for (Coordinate c : allCoords) { + buf.putDouble(c.getX()); + buf.putDouble(c.getY()); + } + if (hasZ) { + writeZSection(buf, allCoords); + } + return buf.array(); + } + + /** Write a 4-double bounding box into {@code buf}. */ + private static void writeBbox(ByteBuffer buf, double[] bbox) { + buf.putDouble(bbox[0]); // xmin + buf.putDouble(bbox[1]); // ymin + buf.putDouble(bbox[2]); // xmax + buf.putDouble(bbox[3]); // ymax + } + + /** + * Write the Z section: zMin (double), zMax (double), then one Z per coordinate. + * NaN Z values are written as 0.0. + */ + private static void writeZSection(ByteBuffer buf, Coordinate[] coords) { + double zMin = Double.MAX_VALUE; + double zMax = -Double.MAX_VALUE; + for (Coordinate c : coords) { + double z = Double.isNaN(c.getZ()) ? 0.0 : c.getZ(); + if (z < zMin) { + zMin = z; + } + if (z > zMax) { + zMax = z; + } + } + buf.putDouble(zMin); + buf.putDouble(zMax); + for (Coordinate c : coords) { + buf.putDouble(Double.isNaN(c.getZ()) ? 0.0 : c.getZ()); + } + } + + /** Reverse a coordinate array (returns a new array, does not modify the original). */ + private static Coordinate[] reverse(Coordinate[] coords) { + Coordinate[] reversed = new Coordinate[coords.length]; + for (int i = 0; i < coords.length; i++) { + reversed[i] = coords[coords.length - 1 - i]; + } + return reversed; + } + + /** Return true if any coordinate in the array has a non-NaN Z value. */ + private static boolean coordsHaveZ(Coordinate[] coords) { + for (Coordinate c : coords) { + if (!Double.isNaN(c.getZ())) { + return true; + } + } + return false; + } + + /** + * Compute [xmin, ymin, xmax, ymax] bounding box from coordinates. + * Ignores Z. + */ + private static double[] bbox2D(Coordinate[] coords) { + double xmin = Double.MAX_VALUE; + double ymin = Double.MAX_VALUE; + double xmax = -Double.MAX_VALUE; + double ymax = -Double.MAX_VALUE; + for (Coordinate c : coords) { + if (c.getX() < xmin) { + xmin = c.getX(); + } + if (c.getX() > xmax) { + xmax = c.getX(); + } + if (c.getY() < ymin) { + ymin = c.getY(); + } + if (c.getY() > ymax) { + ymax = c.getY(); + } + } + return new double[] {xmin, ymin, xmax, ymax}; + } + + /** Read {@code n} (x, y) coordinate pairs from {@code buf} into a Coordinate array. */ + private static Coordinate[] readXYCoords(ByteBuffer buf, int n) { + Coordinate[] coords = new Coordinate[n]; + for (int i = 0; i < n; i++) { + coords[i] = new Coordinate(buf.getDouble(), buf.getDouble()); + } + return coords; + } + + /** + * Read Z values from {@code buf} into an existing coordinate array. + * Skips zMin and zMax (2 doubles) before reading the per-coordinate Z values. + */ + private static void readZIntoCoords(ByteBuffer buf, Coordinate[] coords) { + // skip zMin, zMax + buf.position(buf.position() + 16); + for (Coordinate coord : coords) { + coord.setZ(buf.getDouble()); + } + } + + /** Copy a slice of a coordinate array. */ + private static Coordinate[] copyRange(Coordinate[] src, int from, int to) { + Coordinate[] dst = new Coordinate[to - from]; + System.arraycopy(src, from, dst, 0, to - from); + return dst; + } + + /** Flatten an array of coordinate arrays into a single coordinate array. */ + private static Coordinate[] flattenParts(Coordinate[][] parts) { + int total = 0; + for (Coordinate[] part : parts) { + total += part.length; + } + Coordinate[] all = new Coordinate[total]; + int pos = 0; + for (Coordinate[] part : parts) { + System.arraycopy(part, 0, all, pos, part.length); + pos += part.length; + } + return all; + } +} diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/GeometryUtils.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/GeometryUtils.java index fc7279e3655d..c73da63333c9 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/GeometryUtils.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/GeometryUtils.java @@ -17,29 +17,211 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.MapGeometry; -import com.esri.core.geometry.OperatorImportFromESRIShape; -import com.esri.core.geometry.Polygon; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; import org.apache.hadoop.hive.serde2.objectinspector.primitive.WritableBinaryObjectInspector; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.algorithm.Orientation; +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.io.Ordinate; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKBReader; +import org.locationtech.jts.io.WKBWriter; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.io.WKTWriter; +import org.locationtech.jts.io.geojson.GeoJsonReader; +import org.locationtech.jts.io.geojson.GeoJsonWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Set; public class GeometryUtils { + private static final Logger LOG = LoggerFactory.getLogger(GeometryUtils.class); + private static final int SIZE_WKID = 4; private static final int SIZE_TYPE = 1; public static final int WKID_UNKNOWN = 0; + // Magic byte at offset 4 to distinguish new WKB format from old ESRI format. + // Old format stores OGC type tag (0-6) at byte 4; 0xFF is outside that range. + public static final byte NEW_FORMAT_MAGIC = (byte) 0xFF; + + public static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + + // ThreadLocal JTS readers/writers to avoid per-row allocation overhead. + // These classes are not thread-safe, so ThreadLocal provides safe reuse + // across millions of rows within the same task thread. + private static final ThreadLocal WKB_READER = + ThreadLocal.withInitial(() -> new WKBReader(GEOMETRY_FACTORY)); + private static final ThreadLocal WKB_WRITER = + ThreadLocal.withInitial(WKBWriter::new); + private static final ThreadLocal WKB_WRITER_3D = + ThreadLocal.withInitial(() -> new WKBWriter(3)); + private static final ThreadLocal WKB_WRITER_XYM = ThreadLocal.withInitial(() -> { + WKBWriter w = new WKBWriter(3); + w.setOutputOrdinates(Ordinate.createXYM()); + return w; + }); + private static final ThreadLocal WKB_WRITER_4D = + ThreadLocal.withInitial(() -> new WKBWriter(4)); + private static final ThreadLocal WKT_READER = + ThreadLocal.withInitial(() -> new WKTReader(GEOMETRY_FACTORY)); + private static final ThreadLocal WKT_WRITER = + ThreadLocal.withInitial(WKTWriter::new); + private static final ThreadLocal WKT_WRITER_3D = + ThreadLocal.withInitial(() -> new WKTWriter(3)); + private static final ThreadLocal WKT_WRITER_XYM = ThreadLocal.withInitial(() -> { + WKTWriter w = new WKTWriter(3); + w.setOutputOrdinates(Ordinate.createXYM()); + return w; + }); + private static final ThreadLocal WKT_WRITER_4D = + ThreadLocal.withInitial(() -> new WKTWriter(4)); + private static final ThreadLocal GEOJSON_READER = + ThreadLocal.withInitial(GeoJsonReader::new); + private static final ThreadLocal GEOJSON_WRITER = + ThreadLocal.withInitial(GeoJsonWriter::new); + + /** Get the thread-local WKBReader instance (reused across rows). */ + public static WKBReader wkbReader() { + return WKB_READER.get(); + } + + /** Get the thread-local WKBWriter instance (reused across rows). */ + public static WKBWriter wkbWriter() { + return WKB_WRITER.get(); + } + + /** Get the thread-local WKTReader instance (reused across rows). */ + public static WKTReader wktReader() { + return WKT_READER.get(); + } + + /** Get the thread-local WKTWriter instance (reused across rows). */ + public static WKTWriter wktWriter() { + return WKT_WRITER.get(); + } + + /** Get a dimension-aware WKBWriter that matches the geometry's coordinate type. */ + public static WKBWriter wkbWriterFor(Geometry geom) { + Set ordinates = getOrdinates(geom); + if (ordinates.contains(Ordinate.Z) && ordinates.contains(Ordinate.M)) { + return WKB_WRITER_4D.get(); + } else if (ordinates.contains(Ordinate.Z)) { + return WKB_WRITER_3D.get(); + } else if (ordinates.contains(Ordinate.M)) { + return WKB_WRITER_XYM.get(); + } + return WKB_WRITER.get(); + } + + /** Get a dimension-aware WKTWriter that matches the geometry's coordinate type. */ + public static WKTWriter wktWriterFor(Geometry geom) { + Set ordinates = getOrdinates(geom); + if (ordinates.contains(Ordinate.Z) && ordinates.contains(Ordinate.M)) { + return WKT_WRITER_4D.get(); + } else if (ordinates.contains(Ordinate.Z)) { + return WKT_WRITER_3D.get(); + } else if (ordinates.contains(Ordinate.M)) { + return WKT_WRITER_XYM.get(); + } + return WKT_WRITER.get(); + } + + /** + * Determine the output ordinates present in a geometry by inspecting + * its coordinate sequences. Also validates that Z values are not NaN, + * since JTS base Coordinate class reports dimension=3 even for 2D points. + */ + public static Set getOrdinates(Geometry geom) { + CoordinateSequence seq = getFirstCoordinateSequence(geom); + if (seq == null || seq.size() == 0) { + return Ordinate.createXY(); + } + boolean hasZ = seq.hasZ() && !Double.isNaN(seq.getZ(0)); + boolean hasM = seq.hasM(); + if (hasZ && hasM) { + return Ordinate.createXYZM(); + } else if (hasZ) { + return Ordinate.createXYZ(); + } else if (hasM) { + return Ordinate.createXYM(); + } + return Ordinate.createXY(); + } + + /** + * Get the first non-empty CoordinateSequence from a geometry. + */ + private static CoordinateSequence getFirstCoordinateSequence(Geometry geom) { + if (geom == null || geom.isEmpty()) { + return null; + } + if (geom instanceof Point point) { + return point.getCoordinateSequence(); + } + if (geom instanceof LineString lineString) { + return lineString.getCoordinateSequence(); + } + if (geom instanceof Polygon polygon) { + return polygon.getExteriorRing().getCoordinateSequence(); + } + if (geom instanceof GeometryCollection) { + for (int i = 0; i < geom.getNumGeometries(); i++) { + CoordinateSequence seq = getFirstCoordinateSequence(geom.getGeometryN(i)); + if (seq != null) { + return seq; + } + } + } + return null; + } + + /** Get the thread-local GeoJsonReader instance (reused across rows). */ + public static GeoJsonReader geoJsonReader() { + return GEOJSON_READER.get(); + } + + /** Get the thread-local GeoJsonWriter instance (reused across rows). */ + public static GeoJsonWriter geoJsonWriter() { + return GEOJSON_WRITER.get(); + } + + /** + * Remove all ThreadLocal reader/writer instances for the current thread. + * Call this during UDF close() to prevent memory leaks in long-lived + * thread pools (e.g., HiveServer2 fetch tasks, constant folding). + */ + public static void cleanup() { + WKB_READER.remove(); + WKB_WRITER.remove(); + WKB_WRITER_3D.remove(); + WKB_WRITER_XYM.remove(); + WKB_WRITER_4D.remove(); + WKT_READER.remove(); + WKT_WRITER.remove(); + WKT_WRITER_3D.remove(); + WKT_WRITER_XYM.remove(); + WKT_WRITER_4D.remove(); + GEOJSON_READER.remove(); + GEOJSON_WRITER.remove(); + } + public enum OGCType { UNKNOWN(0), ST_POINT(1), @@ -67,258 +249,321 @@ public int getIndex() { public static final WritableBinaryObjectInspector geometryTransportObjectInspector = PrimitiveObjectInspectorFactory.writableBinaryObjectInspector; - private static final Cache geometryCache = CacheBuilder.newBuilder().weakKeys().build(); + private static final Cache GEOMETRY_CACHE = + CacheBuilder.newBuilder().maximumSize(1024).build(); + + /** + * Detect whether the given bytes use the new WKB format. + */ + public static boolean isNewFormat(BytesWritable geomref) { + return geomref != null && geomref.getLength() > 4 && geomref.getBytes()[4] == NEW_FORMAT_MAGIC; + } /** - * @param geomref1 - * @param geomref2 - * @return return true if both geometries are in the same spatial reference + * Compare spatial references of two geometry byte arrays. */ public static boolean compareSpatialReferences(BytesWritable geomref1, BytesWritable geomref2) { return getWKID(geomref1) == getWKID(geomref2); } - public static BytesWritable geometryToEsriShapeBytesWritable(MapGeometry mapGeometry) { - return serialize(mapGeometry); + /** + * Serialize a JTS Geometry to the new WKB wire format. + * Normalizes polygon ring orientation to OGC standard (exterior CCW, interior CW). + */ + public static BytesWritable geometryToEsriShapeBytesWritable(Geometry geometry) { + if (geometry == null) { + return null; + } + geometry = normalizeRingOrientation(geometry); + return new CachedGeometryBytesWritable(geometry); } - public static BytesWritable geometryToEsriShapeBytesWritable(Geometry geometry, int wkid, OGCType type) { - return serialize(geometry, wkid, type); + /** + * Serialize a JTS Geometry with a specific SRID. + * Normalizes polygon ring orientation to OGC standard (exterior CCW, interior CW). + */ + public static BytesWritable geometryToEsriShapeBytesWritable(Geometry geometry, int wkid) { + if (geometry == null) { + return null; + } + geometry = normalizeRingOrientation(geometry); + geometry.setSRID(wkid); + return new CachedGeometryBytesWritable(geometry); } - public static BytesWritable geometryToEsriShapeBytesWritable(OGCGeometry geometry) { - return new CachedGeometryBytesWritable(geometry); + /** + * Normalize polygon ring orientation to OGC standard: + * exterior rings counter-clockwise (CCW), interior rings clockwise (CW). + * Non-polygon geometries are returned unchanged. + */ + public static Geometry normalizeRingOrientation(Geometry geom) { + if (geom instanceof Polygon polygon) { + return normalizePolygonRings(polygon); + } else if (geom instanceof MultiPolygon mp) { + Polygon[] polys = new Polygon[mp.getNumGeometries()]; + for (int i = 0; i < mp.getNumGeometries(); i++) { + polys[i] = normalizePolygonRings((Polygon) mp.getGeometryN(i)); + } + MultiPolygon result = geom.getFactory().createMultiPolygon(polys); + result.setSRID(geom.getSRID()); + return result; + } + return geom; } - public static OGCGeometry geometryFromEsriShape(BytesWritable geomref) { - // always assume bytes are recycled and can't be cached by using - // geomref.getBytes() as the key - return geometryFromEsriShape(geomref, true); + private static Polygon normalizePolygonRings(Polygon polygon) { + GeometryFactory factory = polygon.getFactory(); + LinearRing shell = polygon.getExteriorRing(); + + // Exterior ring should be CCW + boolean shellChanged = false; + if (shell.getNumPoints() >= 4 && !Orientation.isCCW(shell.getCoordinateSequence())) { + shell = shell.reverse(); + shellChanged = true; + } + + // Interior rings should be CW (not CCW) + int numHoles = polygon.getNumInteriorRing(); + LinearRing[] holes = new LinearRing[numHoles]; + boolean holesChanged = false; + for (int i = 0; i < numHoles; i++) { + LinearRing hole = polygon.getInteriorRingN(i); + if (hole.getNumPoints() >= 4 && Orientation.isCCW(hole.getCoordinateSequence())) { + holes[i] = hole.reverse(); + holesChanged = true; + } else { + holes[i] = hole; + } + } + + if (!shellChanged && !holesChanged) { + return polygon; + } + + Polygon result = factory.createPolygon(shell, holes); + result.setSRID(polygon.getSRID()); + return result; } - public static OGCGeometry geometryFromEsriShape(BytesWritable geomref, boolean bytesRecycled) { + /** + * Deserialize geometry bytes (either old ESRI format or new WKB format) to a JTS Geometry. + */ + public static Geometry geometryFromEsriShape(BytesWritable geomref) { + return geometryFromEsriShape(geomref, true); + } + /** + * Deserialize geometry bytes to a JTS Geometry. + * @param geomref the geometry bytes + * @param bytesRecycled true if the bytes backing the writable may be reused (disables caching) + */ + public static Geometry geometryFromEsriShape(BytesWritable geomref, boolean bytesRecycled) { if (geomref == null) { return null; } - // this geomref might actually be a LazyGeometryBytesWritable which - // means we don't need to deserialize from bytes - if (geomref instanceof CachedGeometryBytesWritable) { - return ((CachedGeometryBytesWritable) geomref).getGeometry(); + if (geomref instanceof CachedGeometryBytesWritable cached) { + return cached.getGeometry(); } - // if geomref bytes are recycled, we can't use the cache because every - // key in the cache will be the same byte array if (!bytesRecycled) { - // check for a cache hit to previously created geometries - OGCGeometry cachedGeom = geometryCache.getIfPresent(geomref); - + Geometry cachedGeom = GEOMETRY_CACHE.getIfPresent(geomref); if (cachedGeom != null) { return cachedGeom; } } - // not in cache or instance of CachedGeometryBytesWritable. now - // need to create the geometry from its bytes - int wkid = getWKID(geomref); - ByteBuffer shapeBuffer = getShapeByteBuffer(geomref); + int length = geomref.getLength(); + byte[] bytes = geomref.getBytes(); - //minimum for a shape, even an empty one, is the 4 byte type record - if (shapeBuffer.limit() < 4) { + if (length < 5) { return null; + } + + Geometry geom; + int srid; + + if (bytes[4] == NEW_FORMAT_MAGIC) { + // New WKB format: bytes 0-3 = SRID (big-endian), byte 4 = 0xFF, bytes 5+ = WKB + srid = ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) + | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + byte[] wkb = Arrays.copyOfRange(bytes, 5, length); + try { + geom = wkbReader().read(wkb); + } catch (ParseException e) { + LOG.warn("Failed to parse WKB geometry", e); + return null; + } } else { - if (shapeBuffer.getInt(0) == Geometry.Type.Unknown.value()) { //empty Geometry, intentional + // Old ESRI format: bytes 0-3 = WKID (little-endian), byte 4 = OGC type (0-6), bytes 5+ = ESRI shape + srid = ByteBuffer.wrap(bytes, 0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + ByteBuffer shapeBuffer = ByteBuffer.wrap(bytes, SIZE_WKID + SIZE_TYPE, length - SIZE_WKID - SIZE_TYPE) + .slice().order(ByteOrder.LITTLE_ENDIAN); + + if (shapeBuffer.limit() < 4) { return null; - } else { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } + } - Geometry esriGeom = OperatorImportFromESRIShape.local().execute(0, Geometry.Type.Unknown, shapeBuffer); - OGCGeometry createdGeom = OGCGeometry.createFromEsriGeometry(esriGeom, spatialReference); + // Check if shape type is Unknown (empty geometry) + if (shapeBuffer.getInt(0) == 0) { + return null; + } - if (!bytesRecycled) { - // only add bytes to cache if we know they aren't being recycled - geometryCache.put(geomref, createdGeom); - } + // Use native EsriShapeConverter to read the old ESRI shape format + try { + geom = EsriShapeConverter.fromEsriShape(shapeBuffer); + } catch (Exception e) { + LOG.warn("Failed to parse ESRI shape geometry", e); + return null; + } + } - return createdGeom; + if (geom != null) { + geom.setSRID(srid); + if (!bytesRecycled) { + GEOMETRY_CACHE.put(geomref, geom); } } + + return geom; } /** - * Gets the geometry type for the given hive geometry bytes - * - * @param geomref reference to hive geometry bytes - * @return OGCType set in the 5th byte of the hive geometry bytes + * Gets the geometry type for the given hive geometry bytes. + * For new WKB format, reads the type directly from the WKB header + * without full deserialization. */ public static OGCType getType(BytesWritable geomref) { - // SIZE_WKID is the offset to the byte that stores the type information - return OGCTypeLookup[geomref.getBytes()[SIZE_WKID]]; + if (geomref == null || geomref.getLength() < 5) { + return OGCType.UNKNOWN; + } + byte[] bytes = geomref.getBytes(); + if (bytes[4] == NEW_FORMAT_MAGIC) { + // New format: bytes 5+ are WKB. WKB layout: byte 5 = byte order, + // bytes 6-9 = geometry type int (in the indicated byte order). + if (geomref.getLength() < 10) { + return OGCType.UNKNOWN; + } + ByteOrder order = (bytes[5] == 1) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; + int wkbType = ByteBuffer.wrap(bytes, 6, 4).order(order).getInt(); + // JTS uses extended WKB: bit 31 (0x80000000) = Z, bit 30 (0x40000000) = M, + // bit 29 (0x20000000) = SRID. Strip these flag bits to get the base type. + // Also handle ISO WKB encoding (type + 1000/2000/3000) for compatibility. + int baseType = wkbType & 0x1FFFFFFF; // strip Z/M/SRID flag bits + if (baseType >= 1000) { + baseType = baseType % 1000; // ISO WKB encoding + } + return switch (baseType) { + case 1 -> OGCType.ST_POINT; + case 2 -> OGCType.ST_LINESTRING; + case 3 -> OGCType.ST_POLYGON; + case 4 -> OGCType.ST_MULTIPOINT; + case 5 -> OGCType.ST_MULTILINESTRING; + case 6 -> OGCType.ST_MULTIPOLYGON; + default -> OGCType.UNKNOWN; + }; + } + int typeIdx = bytes[SIZE_WKID] & 0xFF; + if (typeIdx >= OGCTypeLookup.length) { + return OGCType.UNKNOWN; + } + return OGCTypeLookup[typeIdx]; } /** - * Sets the geometry type (in place) for the given hive geometry bytes - * @param geomref reference to hive geometry bytes - * @param type OGC geometry type + * Infer OGCType from a JTS Geometry. */ - public static void setType(BytesWritable geomref, OGCType type) { - geomref.getBytes()[SIZE_WKID] = (byte) type.getIndex(); + public static OGCType inferOGCType(Geometry geom) { + if (geom == null) { + return OGCType.UNKNOWN; + } + return switch (geom.getGeometryType()) { + case "Point" -> OGCType.ST_POINT; + case "LineString", "LinearRing" -> OGCType.ST_LINESTRING; + case "Polygon" -> OGCType.ST_POLYGON; + case "MultiPoint" -> OGCType.ST_MULTIPOINT; + case "MultiLineString" -> OGCType.ST_MULTILINESTRING; + case "MultiPolygon" -> OGCType.ST_MULTIPOLYGON; + default -> OGCType.UNKNOWN; + }; } /** - * Gets the WKID for the given hive geometry bytes - * - * @param geomref reference to hive geometry bytes - * @return WKID set in the first 4 bytes of the hive geometry bytes + * Gets the WKID/SRID for the given hive geometry bytes. */ public static int getWKID(BytesWritable geomref) { - ByteBuffer bb = ByteBuffer.wrap(geomref.getBytes()); - return bb.getInt(0); + if (geomref == null || geomref.getLength() < 4) { + return WKID_UNKNOWN; + } + byte[] bytes = geomref.getBytes(); + if (geomref.getLength() > 4 && bytes[4] == NEW_FORMAT_MAGIC) { + // New format: big-endian SRID + return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) + | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + } else { + // Old format: little-endian WKID + return ByteBuffer.wrap(bytes, 0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } } /** - * Sets the WKID (in place) for the given hive geometry bytes - * - * @param geomref reference to hive geometry bytes - * @param wkid + * Sets the WKID/SRID (in place) for the given hive geometry bytes. */ public static void setWKID(BytesWritable geomref, int wkid) { - ByteBuffer bb = ByteBuffer.allocate(4); - bb.putInt(wkid); - System.arraycopy(bb.array(), 0, geomref.getBytes(), 0, SIZE_WKID); - } - - public static OGCType getInferredOGCType(Geometry geom) { - switch (geom.getType()) { - case Polygon: - Polygon poly = (Polygon) geom; - // Number of outer rings defines single vs multi - int ringCount = poly.getExteriorRingCount(); - if (ringCount == 1) { - return OGCType.ST_POLYGON; - } else { - return OGCType.ST_MULTIPOLYGON; - } - case Polyline: - return OGCType.ST_MULTILINESTRING; - case MultiPoint: - return OGCType.ST_MULTIPOINT; - case Point: - return OGCType.ST_POINT; - default: - return OGCType.UNKNOWN; - } - } - - private static ByteBuffer getShapeByteBuffer(BytesWritable geomref) { - byte[] geomBytes = geomref.getBytes(); - int offset = SIZE_WKID + SIZE_TYPE; - - return ByteBuffer.wrap(geomBytes, offset, geomBytes.length - offset).slice().order(ByteOrder.LITTLE_ENDIAN); - } - - private static BytesWritable serialize(MapGeometry mapGeometry) { - int wkid = 0; - - SpatialReference spatialRef = mapGeometry.getSpatialReference(); - - if (spatialRef != null) { - wkid = spatialRef.getID(); - } - - Geometry.Type esriType = mapGeometry.getGeometry().getType(); - OGCType ogcType; - - switch (esriType) { - case Point: - ogcType = OGCType.ST_POINT; - break; - case Polyline: - ogcType = OGCType.ST_LINESTRING; - break; - case Polygon: - ogcType = OGCType.ST_POLYGON; - break; - default: - ogcType = OGCType.UNKNOWN; + byte[] bytes = geomref.getBytes(); + if (geomref.getLength() > 4 && bytes[4] == NEW_FORMAT_MAGIC) { + // New format: big-endian + bytes[0] = (byte) (wkid >> 24); + bytes[1] = (byte) (wkid >> 16); + bytes[2] = (byte) (wkid >> 8); + bytes[3] = (byte) wkid; + } else { + // Old format: little-endian + ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + bb.putInt(wkid); + System.arraycopy(bb.array(), 0, bytes, 0, SIZE_WKID); } - - return serialize(mapGeometry.getGeometry(), wkid, ogcType); } - private static BytesWritable serialize(OGCGeometry ogcGeometry) { - int wkid; - try { - wkid = ogcGeometry.SRID(); - } catch (NullPointerException npe) { - wkid = 0; - } - - OGCType ogcType; - String typeName; - try { - typeName = ogcGeometry.geometryType(); - - if (typeName.equals("Point")) - ogcType = OGCType.ST_POINT; - else if (typeName.equals("LineString")) - ogcType = OGCType.ST_LINESTRING; - else if (typeName.equals("Polygon")) - ogcType = OGCType.ST_POLYGON; - else if (typeName.equals("MultiPoint")) - ogcType = OGCType.ST_MULTIPOINT; - else if (typeName.equals("MultiLineString")) - ogcType = OGCType.ST_MULTILINESTRING; - else if (typeName.equals("MultiPolygon")) - ogcType = OGCType.ST_MULTIPOLYGON; - else - ogcType = OGCType.UNKNOWN; - } catch (NullPointerException npe) { - ogcType = OGCType.UNKNOWN; - } - - return serialize(ogcGeometry.getEsriGeometry(), wkid, ogcType); - } - - private static BytesWritable serialize(Geometry geometry, int wkid, OGCType type) { + /** + * Serialize a JTS Geometry to the new WKB wire format bytes. + * Uses dimension-aware WKBWriter to preserve Z/M coordinates. + */ + private static BytesWritable serializeJts(Geometry geometry) { if (geometry == null) { return null; } - - // first get shape buffer for geometry - byte[] shape = GeometryEngine.geometryToEsriShape(geometry); - - if (shape == null) { - return null; - } - - byte[] shapeWithData = new byte[shape.length + SIZE_WKID + SIZE_TYPE]; - - System.arraycopy(shape, 0, shapeWithData, SIZE_WKID + SIZE_TYPE, shape.length); - - BytesWritable hiveGeometryBytes = new BytesWritable(shapeWithData); - - setWKID(hiveGeometryBytes, wkid); - setType(hiveGeometryBytes, type); - - BytesWritable ret = new BytesWritable(shapeWithData); - - return ret; + int srid = geometry.getSRID(); + byte[] wkb = wkbWriterFor(geometry).write(geometry); + + byte[] result = new byte[SIZE_WKID + 1 + wkb.length]; + // Write SRID big-endian + result[0] = (byte) (srid >> 24); + result[1] = (byte) (srid >> 16); + result[2] = (byte) (srid >> 8); + result[3] = (byte) srid; + // Magic byte + result[4] = NEW_FORMAT_MAGIC; + // WKB payload + System.arraycopy(wkb, 0, result, 5, wkb.length); + return new BytesWritable(result); } + /** + * A BytesWritable that caches the JTS Geometry to avoid repeated deserialization. + */ public static class CachedGeometryBytesWritable extends BytesWritable { - OGCGeometry cachedGeom; + private final Geometry cachedGeom; - public CachedGeometryBytesWritable(OGCGeometry geom) { + public CachedGeometryBytesWritable(Geometry geom) { cachedGeom = geom; - super.set(serialize(cachedGeom)); + BytesWritable serialized = serializeJts(cachedGeom); + if (serialized != null) { + super.set(serialized); + } } - public OGCGeometry getGeometry() { + public Geometry getGeometry() { return cachedGeom; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/Haversine.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/Haversine.java index 18fb5db4c96e..dc4718ec9e7c 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/Haversine.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/Haversine.java @@ -23,7 +23,7 @@ import static java.lang.Math.sqrt; import static java.lang.Math.toRadians; -import com.esri.core.geometry.ogc.OGCPoint; +import org.locationtech.jts.geom.Point; // Class is based on Apache Sedona code: // https://github.com/apache/sedona/blob/eee44b509624d9e4022a6dd40d9b07d72a369a20/common/src/main/java/org/apache/sedona/common/sphere/Haversine.java#L43 @@ -56,11 +56,11 @@ public static double distanceMeters(double lon1, double lat1, return distance(lon1, lat1, lon2, lat2, AVG_EARTH_RADIUS_METERS); } - public static double distanceMeters(OGCPoint point1, OGCPoint point2) { - double lon1 = point1.X(); - double lat1 = point1.Y(); - double lon2 = point2.X(); - double lat2 = point2.Y(); + public static double distanceMeters(Point point1, Point point2) { + double lon1 = point1.getX(); + double lat1 = point1.getY(); + double lon2 = point2.getX(); + double lat2 = point2.getY(); return distanceMeters(lon1, lat1, lon2, lat2); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/HiveGeometryOIHelper.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/HiveGeometryOIHelper.java index f20d582dae25..b6628d3c12b6 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/HiveGeometryOIHelper.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/HiveGeometryOIHelper.java @@ -17,8 +17,6 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPoint; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.hive.ql.udf.generic.GenericUDF.DeferredObject; @@ -27,6 +25,8 @@ import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils; import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +38,7 @@ public class HiveGeometryOIHelper { private final int argIndex; private final boolean isConstant; - OGCGeometry constantGeometry; + Geometry constantGeometry; private HiveGeometryOIHelper(ObjectInspector oi, int argIndex) { this.oi = (PrimitiveObjectInspector) oi; @@ -62,13 +62,11 @@ public static HiveGeometryOIHelper create(ObjectInspector oi, int argIndex) thro } public static boolean canCreate(ObjectInspector oi) { - return oi.getCategory() == Category.PRIMITIVE; - } + return oi.getCategory() == Category.PRIMITIVE; + } /** * Gets whether this geometry argument is constant. - * - * @return */ public boolean isConstant() { return isConstant; @@ -76,10 +74,8 @@ public boolean isConstant() { /** * Returns the cached constant geometry object. - * - * @return cache geometry, or null if not constant */ - public OGCGeometry getConstantGeometry() { + public Geometry getConstantGeometry() { return constantGeometry; } @@ -88,14 +84,14 @@ public OGCGeometry getConstantGeometry() { * or returns the cached geometry if argument is constant. * * @param args - * @return OGCPoint or null if not a point + * @return Point or null if not a point * @see #getGeometry(DeferredObject[]) */ - public OGCPoint getPoint(DeferredObject[] args) { - OGCGeometry geometry = getGeometry(args); + public Point getPoint(DeferredObject[] args) { + Geometry geometry = getGeometry(args); - if (geometry instanceof OGCPoint) { - return (OGCPoint) geometry; + if (geometry instanceof Point point) { + return point; } else { return null; } @@ -104,11 +100,8 @@ public OGCPoint getPoint(DeferredObject[] args) { /** * Reads the corresponding geometry from the deferred object list * or returns the cached geometry if argument is constant. - * - * @param args - * @return */ - public OGCGeometry getGeometry(DeferredObject[] args) { + public Geometry getGeometry(DeferredObject[] args) { if (isConstant) { if (constantGeometry == null) { constantGeometry = getGeometry(args[argIndex]); @@ -122,7 +115,7 @@ public OGCGeometry getGeometry(DeferredObject[] args) { } } - private OGCGeometry getGeometry(DeferredObject arg) { + private Geometry getGeometry(DeferredObject arg) { Object writable; try { writable = oi.getPrimitiveWritableObject(arg.get()); @@ -139,7 +132,12 @@ private OGCGeometry getGeometry(DeferredObject arg) { case BINARY: return getGeometryFromBytes((BytesWritable) writable); case STRING: - return OGCGeometry.fromText(writable.toString()); + try { + return GeometryUtils.wktReader().read(writable.toString()); + } catch (Exception e) { + LOG.error("Failed to parse WKT: " + writable.toString(), e); + return null; + } default: return null; } @@ -150,7 +148,7 @@ private OGCGeometry getGeometry(DeferredObject arg) { // always assume bytes are reused until we determine they aren't private boolean bytesReused = true; - private OGCGeometry getGeometryFromBytes(BytesWritable writable) { + private Geometry getGeometryFromBytes(BytesWritable writable) { if (bytesReused) { if (last != null && last != writable) { diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_ConvexHull.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_ConvexHull.java index fcad441604aa..7f4d73e1fc6f 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_ConvexHull.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_ConvexHull.java @@ -17,20 +17,17 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDAF; import org.apache.hadoop.hive.ql.exec.UDAFEvaluator; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.Collections; @Description(name = "ST_Aggr_ConvexHull", value = "_FUNC_(ST_Geometry) - aggregate convex hull of all geometries passed", @@ -42,9 +39,7 @@ public class ST_Aggr_ConvexHull extends UDAF { public static class AggrConvexHullBinaryEvaluator implements UDAFEvaluator { - private final int MAX_BUFFER_SIZE = 1000; - private final ArrayList geometries = new ArrayList(MAX_BUFFER_SIZE); - SpatialReference spatialRef = null; + private final ArrayList geometries = new ArrayList<>(); int firstWKID = -2; /* @@ -52,10 +47,8 @@ public static class AggrConvexHullBinaryEvaluator implements UDAFEvaluator { */ @Override public void init() { - - if (geometries.size() > 0) { - geometries.clear(); - } + geometries.clear(); + firstWKID = -2; } /* @@ -70,17 +63,17 @@ public boolean iterate(BytesWritable geomref) throws HiveException { if (firstWKID == -2) { firstWKID = GeometryUtils.getWKID(geomref); - if (firstWKID != GeometryUtils.WKID_UNKNOWN) { - spatialRef = SpatialReference.create(firstWKID); - } } else if (firstWKID != GeometryUtils.getWKID(geomref)) { LogUtils.Log_SRIDMismatch(LOG, geomref, firstWKID); return false; } - addGeometryToBuffer(geomref); + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom != null) { + geometries.add(geom); + } - return (geometries.size() != 0); + return !geometries.isEmpty(); } /* @@ -92,13 +85,22 @@ public boolean merge(BytesWritable other) throws HiveException { } public BytesWritable terminatePartial() throws HiveException { - maybeAggregateBuffer(true); - if (geometries.size() == 1) { - OGCGeometry rslt = OGCGeometry.createFromEsriGeometry(geometries.get(0), spatialRef); - return GeometryUtils.geometryToEsriShapeBytesWritable(rslt); - } else { + if (geometries.isEmpty()) { return null; } + try { + GeometryCollection collection = GeometryUtils.GEOMETRY_FACTORY + .createGeometryCollection(geometries.toArray(new Geometry[0])); + Geometry result = collection.convexHull(); + int wkid = (firstWKID == -2) ? GeometryUtils.WKID_UNKNOWN : firstWKID; + return GeometryUtils.geometryToEsriShapeBytesWritable(result, wkid); + } catch (Exception e) { + LOG.error("ST_Aggr_ConvexHull failed", e); + } finally { + geometries.clear(); + firstWKID = -2; + } + return null; } /* @@ -109,36 +111,5 @@ public BytesWritable terminate() throws HiveException { return terminatePartial(); } - private void addGeometryToBuffer(BytesWritable geomref) throws HiveException { - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - addGeometryToBuffer(ogcGeometry.getEsriGeometry()); - } - - private void addGeometryToBuffer(Geometry geom) throws HiveException { - geometries.add(geom); - maybeAggregateBuffer(false); - } - - /* - * If the right conditions are met (or force == true), create a convex hull of the geometries - * in the current buffer - */ - private void maybeAggregateBuffer(boolean force) throws HiveException { - - if (force || geometries.size() > MAX_BUFFER_SIZE) { - Geometry[] geomArray = new Geometry[geometries.size()]; - geometries.toArray(geomArray); - geometries.clear(); - - try { - //LOG.trace("performing convexHull"); - Geometry[] convexResult = GeometryEngine.convexHull(geomArray, true); - Collections.addAll(geometries, convexResult); // expect one - } catch (Exception e) { - LOG.error("exception thrown", e); - } - } - } - } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Intersection.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Intersection.java index 28b18d0bce84..a5323edc64e2 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Intersection.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Intersection.java @@ -17,13 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDAF; import org.apache.hadoop.hive.ql.exec.UDAFEvaluator; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,8 +36,7 @@ public class ST_Aggr_Intersection extends UDAF { public static class AggrIntersectionBinaryEvaluator implements UDAFEvaluator { - private OGCGeometry isectGeom = null; - SpatialReference spatialRef = null; + private Geometry isectGeom = null; int firstWKID = -2; /* @@ -60,27 +58,26 @@ public boolean iterate(BytesWritable geomref) throws HiveException { if (firstWKID == -2) { firstWKID = GeometryUtils.getWKID(geomref); - if (firstWKID != GeometryUtils.WKID_UNKNOWN) { - spatialRef = SpatialReference.create(firstWKID); - } } else if (firstWKID != GeometryUtils.getWKID(geomref)) { LogUtils.Log_SRIDMismatch(LOG, geomref, firstWKID); return false; } try { - OGCGeometry rowGeom = GeometryUtils.geometryFromEsriShape(geomref); - rowGeom.setSpatialReference(spatialRef); - if (isectGeom == null) + Geometry rowGeom = GeometryUtils.geometryFromEsriShape(geomref); + if (rowGeom == null) { + return false; + } + if (isectGeom == null) { isectGeom = rowGeom; - else + } else { isectGeom = isectGeom.intersection(rowGeom); + } return true; } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Aggr_Intersection: " + e); return false; } - } /* @@ -97,9 +94,9 @@ public boolean merge(BytesWritable other) throws HiveException { public BytesWritable terminatePartial() throws HiveException { if (isectGeom == null) { return null; - } else { - return GeometryUtils.geometryToEsriShapeBytesWritable(isectGeom); } + int wkid = (firstWKID == -2) ? GeometryUtils.WKID_UNKNOWN : firstWKID; + return GeometryUtils.geometryToEsriShapeBytesWritable(isectGeom, wkid); } public BytesWritable terminate() throws HiveException { diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Union.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Union.java index 3824a51002c2..9178bc39cea5 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Union.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Union.java @@ -17,20 +17,19 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryCursor; -import com.esri.core.geometry.ListeningGeometryCursor; -import com.esri.core.geometry.OperatorUnion; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDAF; import org.apache.hadoop.hive.ql.exec.UDAFEvaluator; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.operation.union.UnaryUnionOp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + @Description(name = "ST_Aggr_Union", value = "_FUNC_(ST_Geometry) - aggregate union of all geometries passed", extended = "Example:\n" + " SELECT _FUNC_(geometry) FROM source; -- return union of all geometries in source") @@ -40,10 +39,8 @@ public class ST_Aggr_Union extends UDAF { public static class AggrUnionBinaryEvaluator implements UDAFEvaluator { - SpatialReference spatialRef = null; int firstWKID = -2; - ListeningGeometryCursor lgc = null; // listening geometry cursor - GeometryCursor xgc = null; // executing geometry cursor + List geomList = new ArrayList<>(); /* * Initialize evaluator @@ -62,32 +59,23 @@ public boolean iterate(BytesWritable geomref) throws HiveException { return false; } - if (xgc == null) { + if (firstWKID == -2) { firstWKID = GeometryUtils.getWKID(geomref); - if (firstWKID != GeometryUtils.WKID_UNKNOWN) { - spatialRef = SpatialReference.create(firstWKID); - } - // Need new geometry cursors both initially and after every terminatePartial(), - // because the geometry cursors can not be re-used after extracting the - // unioned geometry with GeometryCursor.next(). - //Create an empty listener. - lgc = new ListeningGeometryCursor(); - //Obtain union operator - after taking note of spatial reference. - xgc = OperatorUnion.local().execute(lgc, spatialRef, null); } else if (firstWKID != GeometryUtils.getWKID(geomref)) { LogUtils.Log_SRIDMismatch(LOG, geomref, firstWKID); return false; } try { - lgc.tick(GeometryUtils.geometryFromEsriShape(geomref).getEsriGeometry()); // push - xgc.tock(); // tock to match tick + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom != null) { + geomList.add(geom); + } return true; } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Aggr_Union: " + e); return false; } - } /* @@ -102,14 +90,21 @@ public boolean merge(BytesWritable other) throws HiveException { * Return a geometry that is the union of all geometries added up until this point */ public BytesWritable terminatePartial() throws HiveException { + if (geomList.isEmpty()) { + return null; + } try { - Geometry rslt = xgc.next(); - lgc = null; // not reusable - xgc = null; // not reusable - OGCGeometry ogeom = OGCGeometry.createFromEsriGeometry(rslt, spatialRef); - return GeometryUtils.geometryToEsriShapeBytesWritable(ogeom); + Geometry result = UnaryUnionOp.union(geomList); + if (result == null) { + return null; + } + int wkid = (firstWKID == -2) ? GeometryUtils.WKID_UNKNOWN : firstWKID; + return GeometryUtils.geometryToEsriShapeBytesWritable(result, wkid); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Aggr_Union: " + e); + } finally { + geomList.clear(); + firstWKID = -2; } return null; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Area.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Area.java index 1c35c2f277a2..e953aaaa1cf5 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Area.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Area.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,13 +58,13 @@ 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; } - resultDouble.set(ogcGeometry.getEsriGeometry().calculateArea2D()); + resultDouble.set(geom.getArea()); return resultDouble; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsBinary.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsBinary.java index ec74196e7686..b130c9bfaf0f 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsBinary.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsBinary.java @@ -17,14 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; - @Description(name = "ST_AsBinary", value = "_FUNC_(ST_Geometry) - return Well-Known Binary (WKB) representation of geometry\n", extended = "Example:\n" + " SELECT _FUNC_(ST_Point(1, 2)) FROM onerow; -- WKB representation of POINT (1 2)\n") @@ -47,15 +45,14 @@ public BytesWritable 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; } try { - ByteBuffer byteBuf = ogcGeometry.asBinary(); - byte[] byteArr = byteBuf.array(); + byte[] byteArr = GeometryUtils.wkbWriterFor(geom).write(geom); return new BytesWritable(byteArr); } catch (Exception e) { LOG.error(e.getMessage()); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsGeoJson.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsGeoJson.java index 88d34e18f7fb..10d1a036b39d 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsGeoJson.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsGeoJson.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +31,7 @@ + "Note : \n" + " ST_AsGeoJSON outputs the _geometry_ contents but not _crs_.\n" + " ST_AsGeoJSON requires geometry-api-java version 1.1 or later.\n") //@HivePdkUnitTests( -// cases = { +// cases = { // @HivePdkUnitTest( // query = "select ST_AsGeoJSON(ST_point(1, 2))) from onerow", // result = "{\"type\":\"Point\", \"coordinates\":[1.0, 2.0]}" @@ -57,14 +57,14 @@ public Text 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; } try { - String outJson = ogcGeometry.asGeoJson(); + String outJson = GeometryUtils.geoJsonWriter().write(geom); resultText.set(outJson); return resultText; } catch (Exception e) { diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsJson.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsJson.java index 6ae59857432f..02aa0a143f6a 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsJson.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsJson.java @@ -17,12 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; + +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +30,7 @@ extended = "Example:\n" + " SELECT _FUNC_(ST_Point(1.0, 2.0)) from onerow; -- {\"x\":1.0,\"y\":2.0}\n" + " SELECT _FUNC_(ST_SetSRID(ST_Point(1, 1), 4326)) from onerow; -- {\"x\":1.0,\"y\":1.0,\"spatialReference\":{\"wkid\":4326}}") //@HivePdkUnitTests( -// cases = { +// cases = { // @HivePdkUnitTest( // query = "select ST_AsJSON(ST_Point(1, 2)), ST_AsJSON(ST_SetSRID(ST_Point(1, 1), 4326)) from onerow", // result = "{\"x\":1.0,\"y\":2.0} {\"x\":1.0,\"y\":1.0,\"spatialReference\":{\"wkid\":4326}}" @@ -55,14 +54,18 @@ public Text evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry jtsGeom = GeometryUtils.geometryFromEsriShape(geomref); + if (jtsGeom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - Geometry esriGeom = ogcGeometry.getEsriGeometry(); - int wkid = GeometryUtils.getWKID(geomref); - return new Text(GeometryEngine.geometryToJson(wkid, esriGeom)); + try { + int wkid = GeometryUtils.getWKID(geomref); + return new Text(EsriJsonConverter.geometryToEsriJson(jtsGeom, wkid)); + } catch (Exception e) { + LOG.error(e.getMessage()); + return null; + } } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsShape.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsShape.java index ece4f69db7a1..8521e9fd7847 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsShape.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsShape.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; + +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,17 +38,14 @@ public BytesWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry jtsGeom = GeometryUtils.geometryFromEsriShape(geomref); + if (jtsGeom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } try { - // Get Esri shape representation - Geometry esriGeometry = ogcGeometry.getEsriGeometry(); - byte[] esriShape = GeometryEngine.geometryToEsriShape(esriGeometry); - return new BytesWritable(esriShape); + return new BytesWritable(EsriShapeConverter.toEsriShape(jtsGeom)); } catch (Exception e) { LOG.error(e.getMessage()); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsText.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsText.java index 84be547a74ec..0a56fd3de800 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsText.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsText.java @@ -17,13 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.WktExportFlags; -import com.esri.core.geometry.ogc.OGCGeometry; -import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils.OGCType; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +28,7 @@ value = "_FUNC_(ST_Geometry) - return Well-Known Text (WKT) representation of ST_Geometry\n", extended = "Example:\n" + " SELECT _FUNC_(ST_Point(1, 2)) FROM onerow; -- POINT (1 2)\n") //@HivePdkUnitTests( -// cases = { +// cases = { // @HivePdkUnitTest( // query = "SELECT ST_AsText(ST_Point(1, 2)), ST_AsText(ST_MultiPoint(1, 2, 3, 4)) FROM onerow", // result = "POINT (1 2) MULTIPOINT ((1 2), (3 4))" @@ -60,40 +57,21 @@ public Text 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; } - int wktExportFlag = getWktExportFlag(GeometryUtils.getType(geomref)); - try { - // mind: GeometryType with ST_AsText(ST_GeomFromText('MultiLineString((0 80, 0.03 80.04))')) - // return new Text(ogcGeometry.asText()); - return new Text(GeometryEngine.geometryToWkt(ogcGeometry.getEsriGeometry(), wktExportFlag)); + String wkt = GeometryUtils.wktWriterFor(geom).write(geom); + // JTS 1.20 WKTWriter omits the space between dimension qualifier and '(' (e.g. "POINT Z("). + // ISO WKT requires "POINT Z (" with a space. Fix by inserting a space. + wkt = wkt.replace(" ZM(", " ZM (").replace(" Z(", " Z (").replace(" M(", " M ("); + return new Text(wkt); } catch (Exception e) { LOG.error(e.getMessage()); return null; } } - - private int getWktExportFlag(OGCType type) { - switch (type) { - case ST_POLYGON: - return WktExportFlags.wktExportPolygon; - case ST_MULTIPOLYGON: - return WktExportFlags.wktExportMultiPolygon; - case ST_POINT: - return WktExportFlags.wktExportPoint; - case ST_MULTIPOINT: - return WktExportFlags.wktExportMultiPoint; - case ST_LINESTRING: - return WktExportFlags.wktExportLineString; - case ST_MULTILINESTRING: - return WktExportFlags.wktExportMultiLineString; - default: - return WktExportFlags.wktExportDefaults; - } - } -} \ No newline at end of file +} diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Bin.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Bin.java index 61e5985b0b23..4bd34196c99c 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Bin.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Bin.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCPoint; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.metadata.HiveException; @@ -29,6 +28,7 @@ import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorUtils; +import org.locationtech.jts.geom.Point; import java.util.EnumSet; @@ -72,13 +72,13 @@ public Object evaluate(DeferredObject[] args) throws HiveException { bins = new BinUtils(binSize); } - OGCPoint point = geomHelper.getPoint(args); + Point point = geomHelper.getPoint(args); if (point == null) { return null; } - return bins.getId(point.X(), point.Y()); + return bins.getId(point.getX(), point.getY()); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_BinEnvelope.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_BinEnvelope.java index 7bab0b2f3334..fc3ef2e3044f 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_BinEnvelope.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_BinEnvelope.java @@ -17,9 +17,6 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.ogc.OGCPoint; -import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils.OGCType; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.metadata.HiveException; @@ -29,6 +26,9 @@ import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorUtils; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import java.util.EnumSet; @@ -85,7 +85,7 @@ public Object evaluate(DeferredObject[] args) throws HiveException { bins = new BinUtils(binSize); } - Envelope env = new Envelope(); + Envelope env; if (oiBinId != null) { // argument 1 is a number, attempt to get the envelope with bin ID @@ -95,19 +95,20 @@ public Object evaluate(DeferredObject[] args) throws HiveException { } long binId = PrimitiveObjectInspectorUtils.getLong(args[1].get(), oiBinId); - bins.queryEnvelope(binId, env); + env = bins.queryEnvelope(binId); } else { // argument 1 is a geometry, attempt to get the envelope with a point - OGCPoint point = binPoint.getPoint(args); + Point point = binPoint.getPoint(args); if (point == null) { return null; } - bins.queryEnvelope(point.X(), point.Y(), env); + env = bins.queryEnvelope(point.getX(), point.getY()); } - return GeometryUtils.geometryToEsriShapeBytesWritable(env, 0, OGCType.ST_POLYGON); + Geometry polygon = GeometryUtils.GEOMETRY_FACTORY.toGeometry(env); + return GeometryUtils.geometryToEsriShapeBytesWritable(polygon); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Boundary.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Boundary.java index 73d6eee7736e..c869ad58f4f0 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Boundary.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Boundary.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCMultiLineString; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiLineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,15 +56,21 @@ public BytesWritable 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; } try { - OGCGeometry boundGeom = ogcGeometry.boundary(); - if (boundGeom.geometryType().equals("MultiLineString") && ((OGCMultiLineString) boundGeom).numGeometries() == 1) - boundGeom = ((OGCMultiLineString) boundGeom).geometryN(0); // match ST_Boundary/SQL-RDBMS + Geometry boundGeom = geom.getBoundary(); + // match ST_Boundary/SQL-RDBMS: unwrap a single-geometry MultiLineString to LineString + if (boundGeom instanceof MultiLineString && boundGeom.getNumGeometries() == 1) { + boundGeom = boundGeom.getGeometryN(0); + } + // JTS returns LinearRing for polygon boundary; OGC standard requires LineString + if (boundGeom instanceof LinearRing linearRing) { + boundGeom = geom.getFactory().createLineString(linearRing.getCoordinateSequence()); + } return GeometryUtils.geometryToEsriShapeBytesWritable(boundGeom); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Boundary: " + e); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Buffer.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Buffer.java index 98f395bbb045..7dbca66a2382 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Buffer.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Buffer.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,14 +37,13 @@ public BytesWritable evaluate(BytesWritable geometryref1, DoubleWritable distanc return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geometryref1); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geometryref1); + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - OGCGeometry bufferedGeometry = ogcGeometry.buffer(distance.get()); - // TODO persist type information (polygon vs multipolygon) + Geometry bufferedGeometry = geom.buffer(distance.get(), 24); return GeometryUtils.geometryToEsriShapeBytesWritable(bufferedGeometry); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Centroid.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Centroid.java index 430134fe8f3c..4b641340cd6d 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Centroid.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Centroid.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,13 +41,13 @@ public BytesWritable 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; } - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcGeometry.centroid()); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom.getCentroid()); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Contains.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Contains.java index bc0d58a9d4cd..f41d096afaa5 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Contains.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Contains.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorContains; -import com.esri.core.geometry.OperatorSimpleRelation; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.udf.UDFType; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @UDFType(deterministic = true) @Description(name = "ST_Contains", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 contains geometry2", @@ -30,8 +30,13 @@ extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorContains.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.contains(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.contains(geom2); } @Override @@ -39,4 +44,3 @@ public String getDisplayString(String[] args) { return String.format("returns true if %s contains %s", args[0], args[1]); } } - diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ConvexHull.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ConvexHull.java index 8371882ace3b..cfb8c5744385 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ConvexHull.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ConvexHull.java @@ -17,12 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.ogc.OGCGeometry; -import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils.OGCType; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,38 +69,33 @@ public BytesWritable evaluate(BytesWritable... geomrefs) { } } - // now build geometry array to pass to GeometryEngine.union + // build geometry array Geometry[] geomsToProcess = new Geometry[geomrefs.length]; for (int i = 0; i < geomrefs.length; i++) { - //HiveGeometry hiveGeometry = GeometryUtils.geometryFromEsriShape(geomrefs[i]); - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomrefs[i]); + Geometry geom = GeometryUtils.geometryFromEsriShape(geomrefs[i]); - if (ogcGeometry == null) { + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - geomsToProcess[i] = ogcGeometry.getEsriGeometry(); + geomsToProcess[i] = geom; } try { - - Geometry[] geomResult = GeometryEngine.convexHull(geomsToProcess, true); - - if (geomResult.length != 1) { - return null; + Geometry result; + if (geomsToProcess.length == 1) { + result = geomsToProcess[0].convexHull(); + } else { + GeometryCollection collection = + GeometryUtils.GEOMETRY_FACTORY.createGeometryCollection(geomsToProcess); + result = collection.convexHull(); } - Geometry merged = geomResult[0]; - - // we have to infer the type of the differenced geometry because we don't know - // if it's going to end up as a single or multi-part geometry - OGCType inferredType = GeometryUtils.getInferredOGCType(merged); - - return GeometryUtils.geometryToEsriShapeBytesWritable(merged, firstWKID, inferredType); + return GeometryUtils.geometryToEsriShapeBytesWritable(result, firstWKID); } catch (Exception e) { - LogUtils.Log_ExceptionThrown(LOG, "GeometryEngine.convexHull", e); + LogUtils.Log_ExceptionThrown(LOG, "ST_ConvexHull", e); return null; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_CoordDim.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_CoordDim.java index 376628de59cd..120f4c0e5cba 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_CoordDim.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_CoordDim.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,12 +61,23 @@ public IntWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { return null; } - resultInt.set(ogcGeometry.coordinateDimension()); + // Infer coordinate dimension: 2 + (hasZ ? 1 : 0) + (hasM ? 1 : 0) + Coordinate coord = geom.getCoordinate(); + int dim = 2; + if (coord != null) { + if (!Double.isNaN(coord.getZ())) { + dim++; + } + if (!Double.isNaN(coord.getM())) { + dim++; + } + } + resultInt.set(dim); return resultInt; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Crosses.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Crosses.java index 39d48cabccb6..23aa992cf2b8 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Crosses.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Crosses.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorCrosses; -import com.esri.core.geometry.OperatorSimpleRelation; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @Description(name = "ST_Crosses", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 crosses geometry2", @@ -30,8 +30,13 @@ extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorCrosses.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.crosses(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.crosses(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Difference.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Difference.java index c8847b119098..539e0c433369 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Difference.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Difference.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,18 +58,17 @@ public BytesWritable evaluate(BytesWritable geometryref1, BytesWritable geometry return null; } - OGCGeometry ogcGeom1 = GeometryUtils.geometryFromEsriShape(geometryref1); - OGCGeometry ogcGeom2 = GeometryUtils.geometryFromEsriShape(geometryref2); - if (ogcGeom1 == null || ogcGeom2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - OGCGeometry diffGeometry = ogcGeom1.difference(ogcGeom2); + Geometry diffGeometry = geom1.difference(geom2); // we have to infer the type of the differenced geometry because we don't know // if it's going to end up as a single or multi-part geometry - // OGCType inferredType = GeometryUtils.getInferredOGCType(diffGeometry.getEsriGeometry()); return GeometryUtils.geometryToEsriShapeBytesWritable(diffGeometry); } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Dimension.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Dimension.java index 3798b91e882d..54583fe906b6 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Dimension.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Dimension.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,13 +72,13 @@ public IntWritable 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; } - resultInt.set(ogcGeometry.dimension()); + resultInt.set(geom.getDimension()); return resultInt; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Disjoint.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Disjoint.java index 50b70397b114..97ddbebfbc69 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Disjoint.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Disjoint.java @@ -17,12 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorDisjoint; -import com.esri.core.geometry.OperatorSimpleRelation; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @Description(name = "ST_Disjoint", - value = "_FUNC_(ST_Geometry1, ST_Geometry2) - return true if ST_Geometry1 intersects ST_Geometry2", + value = "_FUNC_(ST_Geometry1, ST_Geometry2) - return true if ST_Geometry1 is disjoint from ST_Geometry2", extended = "Example:\n" + "SELECT _FUNC_(ST_LineString(0,0, 0,1), ST_LineString(1,1, 1,0)) from src LIMIT 1; -- return true\n" + "SELECT _FUNC_(ST_LineString(0,0, 1,1), ST_LineString(1,0, 0,1)) from src LIMIT 1; -- return false\n") @@ -30,8 +30,13 @@ public class ST_Disjoint extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorDisjoint.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.disjoint(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.disjoint(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Distance.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Distance.java index ff8f80606b85..e56059b34a35 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Distance.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Distance.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,15 +59,15 @@ public DoubleWritable evaluate(BytesWritable geometryref1, BytesWritable geometr return null; } - OGCGeometry ogcGeom1 = GeometryUtils.geometryFromEsriShape(geometryref1); - OGCGeometry ogcGeom2 = GeometryUtils.geometryFromEsriShape(geometryref2); - if (ogcGeom1 == null || ogcGeom2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } try { - resultDouble.set(ogcGeom1.distance(ogcGeom2)); + resultDouble.set(geom1.distance(geom2)); return resultDouble; } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Distance: " + e); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_DistanceSphere.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_DistanceSphere.java index 8ea5825952b1..a70397fba309 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_DistanceSphere.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_DistanceSphere.java @@ -17,12 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCPoint; - import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils.OGCType; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,13 +55,16 @@ public DoubleWritable evaluate(BytesWritable geometryref1, BytesWritable geometr return null; } - OGCPoint point1 = (OGCPoint) GeometryUtils.geometryFromEsriShape(geometryref1); - OGCPoint point2 = (OGCPoint) GeometryUtils.geometryFromEsriShape(geometryref2); - if (point1 == null || point2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } + Point point1 = (Point) geom1; + Point point2 = (Point) geom2; + try { resultDouble.set(Haversine.distanceMeters(point1, point2)); return resultDouble; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EndPoint.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EndPoint.java index 637861fdc22a..0a158fb478d6 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EndPoint.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EndPoint.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,21 +43,14 @@ public BytesWritable 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; } if (GeometryUtils.getType(geomref) == GeometryUtils.OGCType.ST_LINESTRING) { - MultiPath lines = (MultiPath) (ogcGeometry.getEsriGeometry()); - int wkid = GeometryUtils.getWKID(geomref); - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - return GeometryUtils.geometryToEsriShapeBytesWritable( - OGCGeometry.createFromEsriGeometry(lines.getPoint(lines.getPointCount() - 1), spatialReference)); + return GeometryUtils.geometryToEsriShapeBytesWritable(((LineString) geom).getEndPoint()); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.getType(geomref)); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EnvIntersects.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EnvIntersects.java index 8dbd50baf616..3ac5f9150e07 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EnvIntersects.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EnvIntersects.java @@ -17,12 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,20 +62,14 @@ public BooleanWritable evaluate(BytesWritable geometryref1, BytesWritable geomet return null; } - OGCGeometry ogcGeom1 = GeometryUtils.geometryFromEsriShape(geometryref1); - OGCGeometry ogcGeom2 = GeometryUtils.geometryFromEsriShape(geometryref2); - if (ogcGeom1 == null || ogcGeom2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - Geometry geometry1 = ogcGeom1.getEsriGeometry(); - Geometry geometry2 = ogcGeom2.getEsriGeometry(); - Envelope env1 = new Envelope(), env2 = new Envelope(); - geometry1.queryEnvelope(env1); - geometry2.queryEnvelope(env2); - - resultBoolean.set(env1.isIntersecting(env2)); + resultBoolean.set(geom1.getEnvelopeInternal().intersects(geom2.getEnvelopeInternal())); return resultBoolean; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Envelope.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Envelope.java index 4e2b62f2046c..cacb3fb4603b 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Envelope.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Envelope.java @@ -17,11 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,21 +56,15 @@ public BytesWritable evaluate(BytesWritable geometryref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geometryref); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geometryref); + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } int wkid = GeometryUtils.getWKID(geometryref); - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - Envelope envBound = new Envelope(); - ogcGeometry.getEsriGeometry().queryEnvelope(envBound); - return GeometryUtils - .geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(envBound, spatialReference)); + Geometry envelope = geom.getEnvelope(); + return GeometryUtils.geometryToEsriShapeBytesWritable(envelope, wkid); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Equals.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Equals.java index 170d86245415..85225094fb05 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Equals.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Equals.java @@ -17,9 +17,8 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorEquals; -import com.esri.core.geometry.OperatorSimpleRelation; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; @Description(name = "ST_Equals", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 equals geometry2", @@ -29,8 +28,8 @@ extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorEquals.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.equalsTopo(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ExteriorRing.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ExteriorRing.java index 29ba1eee34a5..35c4ebcbef23 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ExteriorRing.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ExteriorRing.java @@ -17,16 +17,14 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCLineString; -import com.esri.core.geometry.ogc.OGCPolygon; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.Polygon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.Method; - @Description(name = "ST_ExteriorRing", value = "_FUNC_(polygon) - return linestring which is the exterior ring of the polygon", extended = "Example:\n" @@ -58,29 +56,17 @@ public BytesWritable 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; } if (GeometryUtils.getType(geomref) == GeometryUtils.OGCType.ST_POLYGON) { - Method extMethod; - try { - // expect to streamline with updated geometry-api - // OGCLineString extRing = ((OGCPolygon)(ogcGeometry)).exteriorRing(); - extMethod = OGCPolygon.class.getMethod("exteriorRing"); - } catch (Exception e) { - LogUtils.Log_InternalError(LOG, "ST_ExteriorRing: " + e); - try { - extMethod = OGCPolygon.class.getMethod("exterorRing"); - } catch (Exception x) { - LogUtils.Log_InternalError(LOG, "ST_ExteriorRing: " + x); - return null; - } - } try { - OGCLineString extRing = (OGCLineString) (extMethod.invoke(ogcGeometry)); - return GeometryUtils.geometryToEsriShapeBytesWritable(extRing); + LinearRing ring = ((Polygon) geom).getExteriorRing(); + // Return as LineString per OGC spec (not LinearRing which is JTS-specific) + Geometry lineString = geom.getFactory().createLineString(ring.getCoordinateSequence()); + return GeometryUtils.geometryToEsriShapeBytesWritable(lineString); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_ExteriorRing: " + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeodesicLengthWGS84.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeodesicLengthWGS84.java index 0d351d00916d..4c87ea17656e 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeodesicLengthWGS84.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeodesicLengthWGS84.java @@ -17,14 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiLineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,37 +65,46 @@ 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 Haversine + * 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 += Haversine.distanceMeters( + seq.getX(i - 1), seq.getY(i - 1), + seq.getX(i), seq.getY(i)); + } + return length; + } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomCollection.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomCollection.java index 7d5bea4b9970..f8971bcb056e 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomCollection.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomCollection.java @@ -17,14 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,13 +63,11 @@ public BytesWritable evaluate(Text wkwrap, int wkid) throws UDFArgumentException String wkt = wkwrap.toString(); try { - Geometry geomObj = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); - SpatialReference spatialReference = null; // Idea: OGCGeometry.setSpatialReference after .fromText + Geometry geom = GeometryUtils.wktReader().read(wkt); if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); + geom.setSRID(wkid); } - OGCGeometry ogcObj = OGCGeometry.createFromEsriGeometry(geomObj, spatialReference); - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } catch (Exception e) { // IllegalArgumentException, GeometryException LogUtils.Log_InvalidText(LOG, wkt); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoJson.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoJson.java index 24bd8fb7c51d..cb3e25a45e42 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoJson.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoJson.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; @@ -28,6 +27,7 @@ import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,8 +59,8 @@ public Object evaluate(DeferredObject[] arguments) throws HiveException { } try { - OGCGeometry ogcGeom = OGCGeometry.fromGeoJson(json); - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcGeom); + Geometry geom = GeometryUtils.geoJsonReader().read(json); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } catch (Exception e) { LogUtils.Log_InvalidText(LOG, json); } @@ -109,4 +109,8 @@ public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumen return GeometryUtils.geometryTransportObjectInspector; } + @Override + public void close() { + GeometryUtils.cleanup(); + } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromJson.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromJson.java index 74f6a533bf3d..4ba6baccd799 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromJson.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromJson.java @@ -17,8 +17,6 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.fasterxml.jackson.core.JsonFactory; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; @@ -30,13 +28,18 @@ import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; +import org.locationtech.jts.geom.Geometry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + @Description(name = "ST_GeomFromJSON", value = "_FUNC_(json) - construct an ST_Geometry from Esri JSON", extended = "Example:\n" + " SELECT _FUNC_('{\"x\":0.0,\"y\":0.0}') FROM src LIMIT 1; -- constructs ST_Point\n") public class ST_GeomFromJson extends GenericUDF { - static final JsonFactory jsonFactory = new JsonFactory(); + private static final Logger LOG = LoggerFactory.getLogger(ST_GeomFromJson.class); + ObjectInspector jsonOI; @Override @@ -55,10 +58,13 @@ public Object evaluate(DeferredObject[] arguments) throws HiveException { } try { - OGCGeometry ogcGeom = OGCGeometry.fromJson(json); - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcGeom); + Geometry jtsGeom = EsriJsonConverter.esriJsonToGeometry(json); + if (jtsGeom == null) { + return null; + } + return GeometryUtils.geometryToEsriShapeBytesWritable(jtsGeom); } catch (Exception e) { - + LOG.error("Failed to parse Esri JSON", e); } return null; @@ -105,4 +111,8 @@ public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumen return GeometryUtils.geometryTransportObjectInspector; } + @Override + public void close() { + GeometryUtils.cleanup(); + } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromShape.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromShape.java index fa090a301b27..e58b97bd24d4 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromShape.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromShape.java @@ -17,15 +17,16 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils.OGCType; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + @Description(name = "ST_GeomFromShape", value = "_FUNC_(shape) - construct ST_Geometry from Esri shape representation of geometry\n", extended = "Example:\n" @@ -40,29 +41,14 @@ public BytesWritable evaluate(BytesWritable shape) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable shape, int wkid) throws UDFArgumentException { try { - Geometry geometry = GeometryEngine.geometryFromEsriShape(shape.getBytes(), Geometry.Type.Unknown); - switch (geometry.getType()) { - case Point: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.ST_POINT); - - case MultiPoint: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.ST_MULTIPOINT); - - case Line: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.ST_LINESTRING); - - case Polyline: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.ST_MULTILINESTRING); - - case Envelope: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.ST_POLYGON); - - case Polygon: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.ST_MULTIPOLYGON); - - default: - return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.UNKNOWN); + ByteBuffer shapeBuffer = ByteBuffer.wrap(shape.getBytes(), 0, shape.getLength()) + .order(ByteOrder.LITTLE_ENDIAN); + Geometry jtsGeom = EsriShapeConverter.fromEsriShape(shapeBuffer); + if (jtsGeom == null) { + return null; } + jtsGeom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(jtsGeom, wkid); } catch (Exception e) { LogUtils.Log_ExceptionThrown(LOG, "geom-from-shape", e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromText.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromText.java index 6b2d1abdb32e..f1ba6d1f13f7 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromText.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromText.java @@ -17,12 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,13 +71,11 @@ public BytesWritable evaluate(Text wkwrap, int wkid) throws UDFArgumentException String wkt = wkwrap.toString(); try { - SpatialReference spatialReference = null; + Geometry geom = GeometryUtils.wktReader().read(wkt); if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); + geom.setSRID(wkid); } - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(spatialReference); - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } catch (Exception e) { // IllegalArgumentException, GeometryException LogUtils.Log_InvalidText(LOG, wkt); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromWKB.java index f9631c07fe78..1a9831c42794 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_GeomFromWKB", value = "_FUNC_(wkb) - construct an ST_Geometry from OGC well-known binary", @@ -76,16 +74,12 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; + byte[] byteArr = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(byteArr); if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); + geom.setSRID(wkid); } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } catch (Exception e) { // IllegalArgumentException, GeometryException LOG.error(e.getMessage()); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryN.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryN.java index 0c6c8c3154fa..bd54e673c11f 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryN.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryN.java @@ -17,13 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCMultiLineString; -import com.esri.core.geometry.ogc.OGCMultiPoint; -import com.esri.core.geometry.ogc.OGCMultiPolygon; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +39,8 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } @@ -51,7 +48,6 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { int idx = index.get() - 1; // 1-based UI, 0-based engine try { GeometryUtils.OGCType ogcType = GeometryUtils.getType(geomref); - OGCGeometry ogcGeom = null; switch (ogcType) { case ST_POINT: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOINT, ogcType); @@ -62,17 +58,9 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { case ST_POLYGON: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOLYGON, ogcType); return null; - case ST_MULTIPOINT: - ogcGeom = ((OGCMultiPoint) ogcGeometry).geometryN(idx); - break; - case ST_MULTILINESTRING: - ogcGeom = ((OGCMultiLineString) ogcGeometry).geometryN(idx); - break; - case ST_MULTIPOLYGON: - ogcGeom = ((OGCMultiPolygon) ogcGeometry).geometryN(idx); - break; + default: + return GeometryUtils.geometryToEsriShapeBytesWritable(geom.getGeometryN(idx)); } - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcGeom); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_GeometryN: " + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryRelational.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryRelational.java index 0bc483552c68..eeafe95f1e6a 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryRelational.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryRelational.java @@ -17,21 +17,19 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; -import com.esri.core.geometry.OperatorContains; -import com.esri.core.geometry.OperatorSimpleRelation; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.hive.ql.udf.generic.GenericUDF; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; +import org.locationtech.jts.geom.prep.PreparedGeometryFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract class that all simple relational tests (contains, touches, ...) extend from - * */ public abstract class ST_GeometryRelational extends GenericUDF { private static final Logger LOG = LoggerFactory.getLogger(ST_GeometryRelational.class); @@ -43,27 +41,27 @@ public abstract class ST_GeometryRelational extends GenericUDF { private transient HiveGeometryOIHelper geomHelper1; private transient HiveGeometryOIHelper geomHelper2; - private transient OperatorSimpleRelation opSimpleRelation; private transient boolean firstRun = true; + private PreparedGeometry preparedGeom1 = null; - private transient boolean geom1IsAccelerated = false; + /** + * Execute the spatial relationship test between two geometries. + */ + protected abstract boolean executeRelation(Geometry geom1, Geometry geom2); /** - * Operators that extend this should return an instance of - * OperatorSimpleRelation - * - * @return operator for simple relationship tests + * Execute the spatial relationship test using a PreparedGeometry for the first argument. + * Subclasses can override this for optimized prepared geometry operations. */ - protected abstract OperatorSimpleRelation getRelationOperator(); + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return executeRelation(prepGeom1.getGeometry(), geom2); + } @Override public ObjectInspector initialize(ObjectInspector[] OIs) throws UDFArgumentException { - opSimpleRelation = getRelationOperator(); - if (OIs.length != NUM_ARGS) { - throw new UDFArgumentException("The " + opSimpleRelation.getType().toString().toLowerCase() - + " relationship operator takes exactly two arguments"); + throw new UDFArgumentException("Spatial relationship operators take exactly two arguments"); } geomHelper1 = HiveGeometryOIHelper.create(OIs[GEOM_1], GEOM_1); @@ -75,7 +73,7 @@ public ObjectInspector initialize(ObjectInspector[] OIs) throws UDFArgumentExcep } firstRun = true; - geom1IsAccelerated = false; + preparedGeom1 = null; return PrimitiveObjectInspectorFactory.javaBooleanObjectInspector; } @@ -83,30 +81,27 @@ public ObjectInspector initialize(ObjectInspector[] OIs) throws UDFArgumentExcep @Override public Object evaluate(DeferredObject[] args) throws HiveException { - OGCGeometry geom1 = geomHelper1.getGeometry(args); - OGCGeometry geom2 = geomHelper2.getGeometry(args); + Geometry geom1 = geomHelper1.getGeometry(args); + Geometry geom2 = geomHelper2.getGeometry(args); if (geom1 == null || geom2 == null) { return false; } if (firstRun && geomHelper1.isConstant()) { - - // accelerate geometry 1 for quick relation operations since it is constant - geom1IsAccelerated = opSimpleRelation.accelerateGeometry(geom1.getEsriGeometry(), geom1.getEsriSpatialReference(), - GeometryAccelerationDegree.enumMedium); + preparedGeom1 = PreparedGeometryFactory.prepare(geom1); } firstRun = false; - return opSimpleRelation - .execute(geom1.getEsriGeometry(), geom2.getEsriGeometry(), geom1.getEsriSpatialReference(), null); + if (preparedGeom1 != null) { + return executeRelationPrepared(preparedGeom1, geom2); + } + return executeRelation(geom1, geom2); } @Override public void close() { - if (geom1IsAccelerated && geomHelper1 != null && geomHelper1.getConstantGeometry() != null) { - OperatorContains.deaccelerateGeometry(geomHelper1.getConstantGeometry().getEsriGeometry()); - } + GeometryUtils.cleanup(); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_InteriorRingN.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_InteriorRingN.java index c0e93f991c87..34ec0c55a913 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_InteriorRingN.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_InteriorRingN.java @@ -17,12 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCLineString; -import com.esri.core.geometry.ogc.OGCPolygon; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Polygon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,8 +51,8 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } @@ -61,8 +60,7 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { int idx = index.get() - 1; // 1-based UI, 0-based engine if (GeometryUtils.getType(geomref) == GeometryUtils.OGCType.ST_POLYGON) { try { - OGCLineString hole = ((OGCPolygon) (ogcGeometry)).interiorRingN(idx); - return GeometryUtils.geometryToEsriShapeBytesWritable(hole); + return GeometryUtils.geometryToEsriShapeBytesWritable(((Polygon) geom).getInteriorRingN(idx)); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_InteriorRingN: " + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersection.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersection.java index f0d2eff1094b..c1c6a266f077 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersection.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersection.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,16 +47,15 @@ public BytesWritable evaluate(BytesWritable geometryref1, BytesWritable geometry return null; } - OGCGeometry ogcGeom1 = GeometryUtils.geometryFromEsriShape(geometryref1); - OGCGeometry ogcGeom2 = GeometryUtils.geometryFromEsriShape(geometryref2); - if (ogcGeom1 == null || ogcGeom2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - OGCGeometry commonGeom; try { - commonGeom = ogcGeom1.intersection(ogcGeom2); + Geometry commonGeom = geom1.intersection(geom2); return GeometryUtils.geometryToEsriShapeBytesWritable(commonGeom); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Intersection: " + e); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersects.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersects.java index 0db3d30396f5..ec48b9f80236 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersects.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersects.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorIntersects; -import com.esri.core.geometry.OperatorSimpleRelation; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @Description(name = "ST_Intersects", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 intersects geometry2", @@ -30,8 +30,13 @@ public class ST_Intersects extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorIntersects.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.intersects(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.intersects(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Is3D.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Is3D.java index e879bc211c3e..4a115b5286ad 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Is3D.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Is3D.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.Ordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,13 +62,13 @@ public BooleanWritable 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; } - resultBoolean.set(ogcGeometry.is3D()); + resultBoolean.set(GeometryUtils.getOrdinates(geom).contains(Ordinate.Z)); return resultBoolean; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsClosed.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsClosed.java index 9f498b0b085e..a6f227ed39cf 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsClosed.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsClosed.java @@ -17,12 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiLineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,32 +69,29 @@ public BooleanWritable 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; } try { - switch (GeometryUtils.getType(geomref)) { case ST_LINESTRING: + resultBoolean.set(((LineString) geom).isClosed()); + return resultBoolean; case ST_MULTILINESTRING: - MultiPath lines = (MultiPath) (ogcGeometry.getEsriGeometry()); - int nPaths = lines.getPathCount(); - boolean rslt = true; - for (int ix = 0; rslt && ix < nPaths; ix++) { - Point p0 = lines.getPoint(lines.getPathStart(ix)); - Point pf = lines.getPoint(lines.getPathEnd(ix) - 1); - rslt = rslt && pf.equals(p0); // no tolerance - OGC + MultiLineString mls = (MultiLineString) geom; + boolean closed = true; + for (int i = 0; closed && i < mls.getNumGeometries(); i++) { + closed = ((LineString) mls.getGeometryN(i)).isClosed(); } - resultBoolean.set(rslt); + resultBoolean.set(closed); return resultBoolean; default: // ST_IsClosed gives ERROR on Point or Polygon, on Postgres/Oracle LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.getType(geomref)); return null; } - } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_IsClosed" + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsEmpty.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsEmpty.java index 51cc8ae05258..3b107673666e 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsEmpty.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsEmpty.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,14 +59,14 @@ public BooleanWritable 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; } try { - resultBoolean.set(ogcGeometry.isEmpty()); + resultBoolean.set(geom.isEmpty()); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_IsEmpty" + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsMeasured.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsMeasured.java index 6fbd36f7bb90..27a5acfbba5d 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsMeasured.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsMeasured.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.Ordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,13 +62,13 @@ public BooleanWritable 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; } - resultBoolean.set(ogcGeometry.isMeasured()); + resultBoolean.set(GeometryUtils.getOrdinates(geom).contains(Ordinate.M)); return resultBoolean; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsRing.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsRing.java index d0361bb551d5..fcfadaa5114c 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsRing.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsRing.java @@ -17,11 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCLineString; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,24 +61,22 @@ public BooleanWritable 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; } try { - switch (GeometryUtils.getType(geomref)) { case ST_LINESTRING: - OGCLineString lns = (OGCLineString) ogcGeometry; - resultBoolean.set(lns.isClosed() && lns.isSimple()); + LineString ls = (LineString) geom; + resultBoolean.set(ls.isClosed() && ls.isSimple()); return resultBoolean; default: // ST_IsRing gives ERROR on Point, Polygon, or MultiLineString - on Postgres LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.getType(geomref)); return null; } - } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_IsRing" + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsSimple.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsSimple.java index c7de9af59748..9265b5ae5794 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsSimple.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsSimple.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,15 +63,15 @@ public BooleanWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } try { - resultBoolean.set(ogcGeometry.isSimple()); + resultBoolean.set(geom.isSimple()); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_IsSimple" + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Length.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Length.java index 3a79855e9be0..6ce9a3717080 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Length.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Length.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,13 +54,13 @@ 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; } - resultDouble.set(ogcGeometry.getEsriGeometry().calculateLength2D()); + resultDouble.set(geom.getLength()); return resultDouble; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineFromWKB.java index 2b19736b4a07..391a36f5713e 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_LineFromWKB", value = "_FUNC_(wkb) - construct an ST_LineString from OGC well-known binary", @@ -55,17 +53,11 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - if (ogcObj.geometryType().equals("LineString")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + byte[] bytes = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(bytes); + if (geom.getGeometryType().equals("LineString")) { + geom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom, wkid); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineString.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineString.java index a50c93e9b996..b9ffd61e18f1 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineString.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineString.java @@ -17,14 +17,14 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,14 +61,12 @@ public BytesWritable evaluate(DoubleWritable... xyPairs) throws UDFArgumentExcep } try { - Polyline linestring = new Polyline(); - linestring.startPath(xyPairs[0].get(), xyPairs[1].get()); - - for (int i = 2; i < xyPairs.length; i += 2) { - linestring.lineTo(xyPairs[i].get(), xyPairs[i + 1].get()); + Coordinate[] coords = new Coordinate[xyPairs.length / 2]; + for (int i = 0; i < xyPairs.length; i += 2) { + coords[i / 2] = new Coordinate(xyPairs[i].get(), xyPairs[i + 1].get()); } - - return GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(linestring, null)); + Geometry linestring = GeometryUtils.GEOMETRY_FACTORY.createLineString(coords); + return GeometryUtils.geometryToEsriShapeBytesWritable(linestring); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_LineString: " + e); return null; @@ -83,21 +81,16 @@ public BytesWritable evaluate(ArrayList xs, ArrayList points) throws UDFArgumen } try { - Polyline linestring = new Polyline(); - + Coordinate[] coords = new Coordinate[points.size()]; for (int ix = 0; ix < points.size(); ++ix) { BytesWritable geomref = points.get(ix); - OGCGeometry gcur = GeometryUtils.geometryFromEsriShape(geomref); + Geometry gcur = GeometryUtils.geometryFromEsriShape(geomref); if (gcur == null || GeometryUtils.getType(geomref) != GeometryUtils.OGCType.ST_POINT) { if (gcur == null) LogUtils.Log_ArgumentsNull(LOG); @@ -123,14 +115,11 @@ public BytesWritable evaluate(ArrayList points) throws UDFArgumen LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.getType(geomref)); return null; } - if (ix == 0) { - linestring.startPath((Point) gcur.getEsriGeometry()); - } else { - linestring.lineTo((Point) gcur.getEsriGeometry()); - } + Point pt = (Point) gcur; + coords[ix] = pt.getCoordinate(); } - - return GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(linestring, null)); + Geometry linestring = GeometryUtils.GEOMETRY_FACTORY.createLineString(coords); + return GeometryUtils.geometryToEsriShapeBytesWritable(linestring); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_LineString: " + e); return null; @@ -141,10 +130,9 @@ public BytesWritable evaluate(ArrayList points) throws UDFArgumen public BytesWritable evaluate(Text wkwrap) throws UDFArgumentException { String wkt = wkwrap.toString(); try { - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(null); - if (ogcObj.geometryType().equals("LineString")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + Geometry geom = GeometryUtils.wktReader().read(wkt); + if (geom.getGeometryType().equals("LineString")) { + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_M.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_M.java index ef176e937b0a..54fe8cca0bf4 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_M.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_M.java @@ -17,11 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPoint; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,19 +60,20 @@ public DoubleWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { - return null; - } - if (!ogcGeometry.isMeasured()) { - LogUtils.Log_NotMeasured(LOG); + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { return null; } switch (GeometryUtils.getType(geomref)) { case ST_POINT: - OGCPoint pt = (OGCPoint) ogcGeometry; - resultDouble.set(pt.M()); + Coordinate coord = ((Point) geom).getCoordinate(); + double m = coord.getM(); + if (Double.isNaN(m)) { + LogUtils.Log_NotMeasured(LOG); + return null; + } + resultDouble.set(m); return resultDouble; default: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.getType(geomref)); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MLineFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MLineFromWKB.java index 01e5cd70d02a..f9922c31168f 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MLineFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MLineFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_MLineFromWKB", value = "_FUNC_(wkb) - construct an ST_MultiLineString from OGC well-known binary", @@ -55,18 +53,12 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - String gType = ogcObj.geometryType(); + byte[] bytes = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(bytes); + String gType = geom.getGeometryType(); if (gType.equals("MultiLineString") || gType.equals("LineString")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + geom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom, wkid); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTILINESTRING, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPointFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPointFromWKB.java index 2fe92022bb35..b4de15bcb345 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPointFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPointFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_MPointFromWKB", value = "_FUNC_(wkb) - construct an ST_MultiPoint from OGC well-known binary", @@ -55,20 +53,14 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - String gType = ogcObj.geometryType(); + byte[] bytes = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(bytes); + String gType = geom.getGeometryType(); if (gType.equals("MultiPoint") || gType.equals("Point")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + geom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom, wkid); } else { - LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.OGCType.UNKNOWN); + LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOINT, GeometryUtils.OGCType.UNKNOWN); return null; } } catch (Exception e) { // IllegalArgumentException, GeometryException diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPolyFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPolyFromWKB.java index 1092226df060..3c00ecbdc9b2 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPolyFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPolyFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_MPolyFromWKB", value = "_FUNC_(wkb) - construct an ST_MultiPolygon from OGC well-known binary", @@ -55,18 +53,12 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - String gType = ogcObj.geometryType(); + byte[] bytes = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(bytes); + String gType = geom.getGeometryType(); if (gType.equals("MultiPolygon") || gType.equals("Polygon")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + geom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom, wkid); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOLYGON, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxM.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxM.java index 1964ca43723a..2dfdaa74f05f 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxM.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxM.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.CoordinateFilter; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,17 +64,26 @@ 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; } - if (!ogcGeometry.isMeasured()) { + + double[] max = {Double.NaN}; + geom.apply((CoordinateFilter) coord -> { + double m = coord.getM(); + if (!Double.isNaN(m)) { + max[0] = Double.isNaN(max[0]) ? m : Math.max(max[0], m); + } + }); + + if (Double.isNaN(max[0])) { LogUtils.Log_NotMeasured(LOG); return null; } - resultDouble.set(ogcGeometry.MaxMeasure()); + resultDouble.set(max[0]); return resultDouble; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxX.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxX.java index e1000c2c8f48..973893d733ff 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxX.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxX.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,15 +71,17 @@ 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; } - Envelope envBound = new Envelope(); - ogcGeometry.getEsriGeometry().queryEnvelope(envBound); - resultDouble.set(envBound.getXMax()); + if (geom.isEmpty()) { + resultDouble.set(Double.NaN); + } else { + resultDouble.set(geom.getEnvelopeInternal().getMaxX()); + } return resultDouble; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxY.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxY.java index 3ffb17e30349..b832d5293a49 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxY.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxY.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,15 +71,17 @@ 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; } - Envelope envBound = new Envelope(); - ogcGeometry.getEsriGeometry().queryEnvelope(envBound); - resultDouble.set(envBound.getYMax()); + if (geom.isEmpty()) { + resultDouble.set(Double.NaN); + } else { + resultDouble.set(geom.getEnvelopeInternal().getMaxY()); + } return resultDouble; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxZ.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxZ.java index c1f526ce9b35..9901f77a4081 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxZ.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxZ.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.CoordinateFilter; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,17 +64,26 @@ 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; } - if (!ogcGeometry.is3D()) { + + double[] max = {Double.NaN}; + geom.apply((CoordinateFilter) coord -> { + double z = coord.getZ(); + if (!Double.isNaN(z)) { + max[0] = Double.isNaN(max[0]) ? z : Math.max(max[0], z); + } + }); + + if (Double.isNaN(max[0])) { LogUtils.Log_Not3D(LOG); return null; } - resultDouble.set(ogcGeometry.MaxZ()); + resultDouble.set(max[0]); return resultDouble; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinM.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinM.java index a202754bfba8..94f2a2673d52 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinM.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinM.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.CoordinateFilter; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,17 +64,26 @@ 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; } - if (!ogcGeometry.isMeasured()) { + + double[] min = {Double.NaN}; + geom.apply((CoordinateFilter) coord -> { + double m = coord.getM(); + if (!Double.isNaN(m)) { + min[0] = Double.isNaN(min[0]) ? m : Math.min(min[0], m); + } + }); + + if (Double.isNaN(min[0])) { LogUtils.Log_NotMeasured(LOG); return null; } - resultDouble.set(ogcGeometry.MinMeasure()); + resultDouble.set(min[0]); return resultDouble; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinX.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinX.java index 10d86a77b642..b34b4df56920 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinX.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinX.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,15 +71,17 @@ 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; } - Envelope envBound = new Envelope(); - ogcGeometry.getEsriGeometry().queryEnvelope(envBound); - resultDouble.set(envBound.getXMin()); + if (geom.isEmpty()) { + resultDouble.set(Double.NaN); + } else { + resultDouble.set(geom.getEnvelopeInternal().getMinX()); + } return resultDouble; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinY.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinY.java index ea90923f50fb..2abedd07beb5 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinY.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinY.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,15 +71,17 @@ 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; } - Envelope envBound = new Envelope(); - ogcGeometry.getEsriGeometry().queryEnvelope(envBound); - resultDouble.set(envBound.getYMin()); + if (geom.isEmpty()) { + resultDouble.set(Double.NaN); + } else { + resultDouble.set(geom.getEnvelopeInternal().getMinY()); + } return resultDouble; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinZ.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinZ.java index 5fbebc4a7cb3..6b3dfec9c5c3 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinZ.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinZ.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.CoordinateFilter; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,17 +64,26 @@ 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; } - if (!ogcGeometry.is3D()) { + + double[] min = {Double.NaN}; + geom.apply((CoordinateFilter) coord -> { + double z = coord.getZ(); + if (!Double.isNaN(z)) { + min[0] = Double.isNaN(min[0]) ? z : Math.min(min[0], z); + } + }); + + if (Double.isNaN(min[0])) { LogUtils.Log_Not3D(LOG); return null; } - resultDouble.set(ogcGeometry.MinZ()); + resultDouble.set(min[0]); return resultDouble; } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiLineString.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiLineString.java index 0fc4ddcab6e2..9839b85cdad8 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiLineString.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiLineString.java @@ -17,14 +17,15 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,24 +62,25 @@ public BytesWritable evaluate(List... multipaths) throws UDFArgu } try { - Polyline mPolyline = new Polyline(); - + LineString[] lineStrings = new LineString[multipaths.length]; int arg_idx = 0; + for (List multipath : multipaths) { if (multipath.size() % 2 != 0) { LogUtils.Log_VariableArgumentLengthXY(LOG, arg_idx); return null; } - mPolyline.startPath(multipath.get(0).get(), multipath.get(1).get()); - - for (int i = 2; i < multipath.size(); i += 2) { - mPolyline.lineTo(multipath.get(i).get(), multipath.get(i + 1).get()); + Coordinate[] coords = new Coordinate[multipath.size() / 2]; + for (int i = 0; i < multipath.size(); i += 2) { + coords[i / 2] = new Coordinate(multipath.get(i).get(), multipath.get(i + 1).get()); } + lineStrings[arg_idx] = GeometryUtils.GEOMETRY_FACTORY.createLineString(coords); arg_idx++; } - return GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(mPolyline, null, true)); + Geometry mLineString = GeometryUtils.GEOMETRY_FACTORY.createMultiLineString(lineStrings); + return GeometryUtils.geometryToEsriShapeBytesWritable(mLineString); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_MultiLineString: " + e); return null; @@ -89,10 +91,9 @@ public BytesWritable evaluate(List... multipaths) throws UDFArgu public BytesWritable evaluate(Text wkwrap) throws UDFArgumentException { String wkt = wkwrap.toString(); try { - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(null); - if (ogcObj.geometryType().equals("MultiLineString")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + Geometry geom = GeometryUtils.wktReader().read(wkt); + if (geom.getGeometryType().equals("MultiLineString")) { + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTILINESTRING, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPoint.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPoint.java index 3ede57503776..779013efd305 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPoint.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPoint.java @@ -17,14 +17,14 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +34,7 @@ extended = "Example:\n" + " SELECT _FUNC_(1, 1, 2, 2, 3, 3) from src LIMIT 1; -- multipoint with 3 points\n" + " SELECT _FUNC_('MULTIPOINT ((10 40), (40 30))') from src LIMIT 1; -- multipoint of 2 points") //@HivePdkUnitTests( -// cases = { +// cases = { // @HivePdkUnitTest( // query = "select st_asjson(st_multipoint(1, 1, 2, 2, 3, 3)) from onerow", // result = "{\"points\":[[1.0,1.0],[2.0,2.0],[3.0,3.0]]}" @@ -63,13 +63,12 @@ public BytesWritable evaluate(DoubleWritable... xyPairs) throws UDFArgumentLengt } try { - MultiPoint mPoint = new MultiPoint(); - + Coordinate[] coords = new Coordinate[xyPairs.length / 2]; for (int i = 0; i < xyPairs.length; i += 2) { - mPoint.add(xyPairs[i].get(), xyPairs[i + 1].get()); + coords[i / 2] = new Coordinate(xyPairs[i].get(), xyPairs[i + 1].get()); } - - return GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(mPoint, null, true)); + Geometry mPoint = GeometryUtils.GEOMETRY_FACTORY.createMultiPointFromCoords(coords); + return GeometryUtils.geometryToEsriShapeBytesWritable(mPoint); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_MultiPoint: " + e); return null; @@ -80,10 +79,9 @@ public BytesWritable evaluate(DoubleWritable... xyPairs) throws UDFArgumentLengt public BytesWritable evaluate(Text wkwrap) throws UDFArgumentException { String wkt = wkwrap.toString(); try { - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(null); - if (ogcObj.geometryType().equals("MultiPoint")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + Geometry geom = GeometryUtils.wktReader().read(wkt); + if (geom.getGeometryType().equals("MultiPoint")) { + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOINT, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPolygon.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPolygon.java index 8f981a3c4024..406697f96b62 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPolygon.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPolygon.java @@ -17,13 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +37,7 @@ + " SELECT _FUNC_(array(1, 1, 1, 2, 2, 2, 2, 1), array(3, 3, 3, 4, 4, 4, 4, 3)) from src LIMIT 1;\n" + " SELECT _FUNC_('multipolygon (((0 0, 0 1, 1 0, 0 0)), ((2 2, 2 3, 3 2, 2 2)))') from src LIMIT 1;") //@HivePdkUnitTests( -// cases = { +// cases = { // @HivePdkUnitTest( // query = "select st_asjson(st_multipolygon(array(1, 1, 1, 2, 2, 2, 2, 1), array(3, 3, 3, 4, 4, 4, 4, 3))) from onerow;", // result = "{\"rings\":[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[2.0,1.0],[1.0,1.0]],[[3.0,3.0],[3.0,4.0],[4.0,4.0],[4.0,3.0],[3.0,3.0]]]}" @@ -101,10 +101,9 @@ public BytesWritable evaluate(List... multipaths) throws UDFArgu public BytesWritable evaluate(Text wkwrap) throws UDFArgumentException { String wkt = wkwrap.toString(); try { - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(null); - if (ogcObj.geometryType().equals("MultiPolygon")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + Geometry geom = GeometryUtils.wktReader().read(wkt); + if (geom.getGeometryType().equals("MultiPolygon")) { + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOLYGON, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumGeometries.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumGeometries.java index cfca1b5294ac..4d1bfdadf845 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumGeometries.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumGeometries.java @@ -17,13 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCMultiLineString; -import com.esri.core.geometry.ogc.OGCMultiPoint; -import com.esri.core.geometry.ogc.OGCMultiPolygon; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,8 +40,8 @@ public IntWritable 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; } @@ -61,18 +58,10 @@ public IntWritable evaluate(BytesWritable geomref) { case ST_POLYGON: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_MULTIPOLYGON, ogcType); return null; - case ST_MULTIPOINT: - resultInt.set(((OGCMultiPoint) ogcGeometry).numGeometries()); - break; - case ST_MULTILINESTRING: - resultInt.set(((OGCMultiLineString) ogcGeometry).numGeometries()); - break; - case ST_MULTIPOLYGON: - resultInt.set(((OGCMultiPolygon) ogcGeometry).numGeometries()); + default: + resultInt.set(geom.getNumGeometries()); break; } - } catch (ClassCastException cce) { // single vs Multi geometry type - resultInt.set(1); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_NumGeometries: " + e); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumInteriorRing.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumInteriorRing.java index 01fb8264ceab..419022d4815a 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumInteriorRing.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumInteriorRing.java @@ -17,11 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPolygon; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Polygon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,14 +57,14 @@ public IntWritable 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; } if (GeometryUtils.getType(geomref) == GeometryUtils.OGCType.ST_POLYGON) { try { - resultInt.set(((OGCPolygon) (ogcGeometry)).numInteriorRing()); + resultInt.set(((Polygon) geom).getNumInteriorRing()); return resultInt; } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_NumInteriorRing: " + e); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumPoints.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumPoints.java index 2ec95c090cb3..c1f8f0bced54 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumPoints.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumPoints.java @@ -17,14 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.Polygon; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,28 +68,13 @@ public IntWritable 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: - resultInt.set(esriGeom.isEmpty() ? 0 : 1); - break; - case MultiPoint: - resultInt.set(((MultiPoint) (esriGeom)).getPointCount()); - break; - case Polygon: - Polygon polygon = (Polygon) (esriGeom); - resultInt.set(polygon.getPointCount() + polygon.getPathCount()); - break; - default: - resultInt.set(((MultiPath) (esriGeom)).getPointCount()); - break; - } + resultInt.set(geom.getNumPoints()); return resultInt; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Overlaps.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Overlaps.java index 0810f0cd595c..e2ce0f104a17 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Overlaps.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Overlaps.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorOverlaps; -import com.esri.core.geometry.OperatorSimpleRelation; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @Description(name = "ST_Overlaps", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 overlaps geometry2", @@ -30,8 +30,13 @@ public class ST_Overlaps extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorOverlaps.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.overlaps(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.overlaps(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Point.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Point.java index 75969e38eca3..90073e49da3b 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Point.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Point.java @@ -17,13 +17,15 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +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.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,14 +66,18 @@ public BytesWritable evaluate(DoubleWritable x, DoubleWritable y, DoubleWritable return null; } try { - Point stPt = new Point(x.get(), y.get()); - if (z != null) - stPt.setZ(z.get()); - if (m != null) - stPt.setM(m.get()); - BytesWritable ret = - GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(stPt, null)); - return ret; + Coordinate coord; + if (m != null && z != null) { + coord = new CoordinateXYZM(x.get(), y.get(), z.get(), m.get()); + } else if (z != null) { + coord = new Coordinate(x.get(), y.get(), z.get()); + } else if (m != null) { + coord = new CoordinateXYM(x.get(), y.get(), m.get()); + } else { + coord = new Coordinate(x.get(), y.get()); + } + Geometry stPt = GeometryUtils.GEOMETRY_FACTORY.createPoint(coord); + return GeometryUtils.geometryToEsriShapeBytesWritable(stPt); } catch (Exception e) { //LogUtils.Log_InternalError(LOG, "ST_Point: " + e); return null; @@ -82,10 +88,9 @@ public BytesWritable evaluate(DoubleWritable x, DoubleWritable y, DoubleWritable public BytesWritable evaluate(Text wkwrap) throws UDFArgumentException { String wkt = wkwrap.toString(); try { - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(null); - if (ogcObj.geometryType().equals("Point")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + Geometry geom = GeometryUtils.wktReader().read(wkt); + if (geom.getGeometryType().equals("Point")) { + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointFromWKB.java index 5bd428d0cbcd..cd41d4dcafff 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_PointFromWKB", value = "_FUNC_(wkb) - construct an ST_Point from OGC well-known binary", @@ -55,17 +53,11 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - if (ogcObj.geometryType().equals("Point")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + byte[] bytes = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(bytes); + if (geom.getGeometryType().equals("Point")) { + geom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom, wkid); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointN.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointN.java index 7300fab3cde1..194da51d087e 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointN.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointN.java @@ -17,14 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,33 +65,32 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { 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(); - Point pn = null; int idx = index.get(); idx = (idx == 0) ? 0 : idx - 1; // consistency with SDE ST_Geometry - switch (esriGeom.getType()) { - case Line: - case Polyline: - MultiPath lines = (MultiPath) (esriGeom); + + Point pn = null; + switch (GeometryUtils.getType(geomref)) { + case ST_LINESTRING: + LineString ls = (LineString) geom; try { - pn = lines.getPoint(idx); + pn = ls.getPointN(idx); } catch (Exception e) { - LogUtils.Log_InvalidIndex(LOG, idx + 1, 1, lines.getPointCount()); + LogUtils.Log_InvalidIndex(LOG, idx + 1, 1, ls.getNumPoints()); return null; } break; - case MultiPoint: - MultiPoint mp = (MultiPoint) (esriGeom); + case ST_MULTIPOINT: + MultiPoint mp = (MultiPoint) geom; try { - pn = mp.getPoint(idx); + pn = (Point) mp.getGeometryN(idx); } catch (Exception e) { - LogUtils.Log_InvalidIndex(LOG, idx + 1, 1, mp.getPointCount()); + LogUtils.Log_InvalidIndex(LOG, idx + 1, 1, mp.getNumGeometries()); return null; } break; @@ -100,7 +98,6 @@ public BytesWritable evaluate(BytesWritable geomref, IntWritable index) { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.getType(geomref)); return null; } - return GeometryUtils - .geometryToEsriShapeBytesWritable(pn, GeometryUtils.getWKID(geomref), GeometryUtils.OGCType.ST_POINT); + return GeometryUtils.geometryToEsriShapeBytesWritable(pn); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointZ.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointZ.java index 5018a2609ab5..67c27bb7d201 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointZ.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointZ.java @@ -17,11 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateXYZM; +import org.locationtech.jts.geom.Geometry; @Description(name = "ST_PointZ", value = "_FUNC_(x, y, z) - constructor for 3D point", @@ -37,9 +38,13 @@ public BytesWritable evaluate(DoubleWritable x, DoubleWritable y, DoubleWritable if (x == null || y == null || z == null) { return null; } - Point stPt = new Point(x.get(), y.get(), z.get()); - if (m != null) - stPt.setM(m.get()); - return GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(stPt, null)); + Coordinate coord; + if (m != null) { + coord = new CoordinateXYZM(x.get(), y.get(), z.get(), m.get()); + } else { + coord = new Coordinate(x.get(), y.get(), z.get()); + } + Geometry stPt = GeometryUtils.GEOMETRY_FACTORY.createPoint(coord); + return GeometryUtils.geometryToEsriShapeBytesWritable(stPt); } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PolyFromWKB.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PolyFromWKB.java index 6c9f4ef68ff6..75083b47cb3d 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PolyFromWKB.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PolyFromWKB.java @@ -17,15 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; +import java.util.Arrays; @Description(name = "ST_PolyFromWKB", value = "_FUNC_(wkb) - construct an ST_Polygon from OGC well-known binary", @@ -55,17 +53,11 @@ public BytesWritable evaluate(BytesWritable wkb) throws UDFArgumentException { public BytesWritable evaluate(BytesWritable wkb, int wkid) throws UDFArgumentException { try { - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - byte[] byteArr = wkb.getBytes(); - ByteBuffer byteBuf = ByteBuffer.allocate(byteArr.length); - byteBuf.put(byteArr); - OGCGeometry ogcObj = OGCGeometry.fromBinary(byteBuf); - ogcObj.setSpatialReference(spatialReference); - if (ogcObj.geometryType().equals("Polygon")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + byte[] bytes = Arrays.copyOf(wkb.getBytes(), wkb.getLength()); + Geometry geom = GeometryUtils.wkbReader().read(bytes); + if (geom.getGeometryType().equals("Polygon")) { + geom.setSRID(wkid); + return GeometryUtils.geometryToEsriShapeBytesWritable(geom, wkid); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POLYGON, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Polygon.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Polygon.java index 8cf407e3176b..3ac7eedee1f6 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Polygon.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Polygon.java @@ -17,13 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,10 +87,9 @@ public BytesWritable evaluate(DoubleWritable... xyPairs) throws UDFArgumentLengt public BytesWritable evaluate(Text wkwrap) throws UDFArgumentException { String wkt = wkwrap.toString(); try { - OGCGeometry ogcObj = OGCGeometry.fromText(wkt); - ogcObj.setSpatialReference(null); - if (ogcObj.geometryType().equals("Polygon")) { - return GeometryUtils.geometryToEsriShapeBytesWritable(ogcObj); + Geometry geom = GeometryUtils.wktReader().read(wkt); + if (geom.getGeometryType().equals("Polygon")) { + return GeometryUtils.geometryToEsriShapeBytesWritable(geom); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POLYGON, GeometryUtils.OGCType.UNKNOWN); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Relate.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Relate.java index 0c2ac0d1a6a2..0d7030025e83 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Relate.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Relate.java @@ -17,10 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BooleanWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,15 +48,15 @@ public BooleanWritable evaluate(BytesWritable geometryref1, BytesWritable geomet return null; } - OGCGeometry ogcGeom1 = GeometryUtils.geometryFromEsriShape(geometryref1); - OGCGeometry ogcGeom2 = GeometryUtils.geometryFromEsriShape(geometryref2); - if (ogcGeom1 == null || ogcGeom2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } try { - resultBoolean.set(ogcGeom1.relate(ogcGeom2, relation)); + resultBoolean.set(geom1.relate(geom2, relation)); return resultBoolean; } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_Relate: " + e); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_StartPoint.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_StartPoint.java index 8d876dfb2cd8..6c6f94fd6cc5 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_StartPoint.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_StartPoint.java @@ -17,11 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,21 +43,14 @@ public BytesWritable 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; } if (GeometryUtils.getType(geomref) == GeometryUtils.OGCType.ST_LINESTRING) { - MultiPath lines = (MultiPath) (ogcGeometry.getEsriGeometry()); - int wkid = GeometryUtils.getWKID(geomref); - SpatialReference spatialReference = null; - if (wkid != GeometryUtils.WKID_UNKNOWN) { - spatialReference = SpatialReference.create(wkid); - } - return GeometryUtils - .geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(lines.getPoint(0), spatialReference)); + return GeometryUtils.geometryToEsriShapeBytesWritable(((LineString) geom).getStartPoint()); } else { LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_LINESTRING, GeometryUtils.getType(geomref)); return null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_SymmetricDiff.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_SymmetricDiff.java index 3d72b5180fd8..b3cbc19b6386 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_SymmetricDiff.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_SymmetricDiff.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,15 +63,15 @@ public BytesWritable evaluate(BytesWritable geometryref1, BytesWritable geometry return null; } - OGCGeometry ogcGeom1 = GeometryUtils.geometryFromEsriShape(geometryref1); - OGCGeometry ogcGeom2 = GeometryUtils.geometryFromEsriShape(geometryref2); - if (ogcGeom1 == null || ogcGeom2 == null) { + Geometry geom1 = GeometryUtils.geometryFromEsriShape(geometryref1); + Geometry geom2 = GeometryUtils.geometryFromEsriShape(geometryref2); + if (geom1 == null || geom2 == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } try { - OGCGeometry diffGeometry = ogcGeom1.symDifference(ogcGeom2); + Geometry diffGeometry = geom1.symDifference(geom2); return GeometryUtils.geometryToEsriShapeBytesWritable(diffGeometry); } catch (Exception e) { LogUtils.Log_InternalError(LOG, "ST_SymmetricDiff: " + e); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Touches.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Touches.java index 7881c48430dd..3783ea855028 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Touches.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Touches.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorSimpleRelation; -import com.esri.core.geometry.OperatorTouches; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @Description(name = "ST_Touches", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 touches geometry2", @@ -30,8 +30,13 @@ public class ST_Touches extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorTouches.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.touches(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.touches(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Union.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Union.java index 847881884408..0eaa554858e7 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Union.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Union.java @@ -17,16 +17,16 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; -import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils.OGCType; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.operation.union.UnaryUnionOp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + @Description(name = "ST_Union", value = "_FUNC_(ST_Geometry, ST_Geometry, ...) - returns an ST_Geometry as the union of the supplied ST_Geometries", extended = @@ -59,8 +59,6 @@ public BytesWritable evaluate(BytesWritable... geomrefs) { int firstWKID = 0; - SpatialReference spatialRef = null; - // validate spatial references and geometries first for (int i = 0; i < geomrefs.length; i++) { @@ -73,44 +71,34 @@ public BytesWritable evaluate(BytesWritable... geomrefs) { if (i == 0) { firstWKID = GeometryUtils.getWKID(geomref); - if (firstWKID != GeometryUtils.WKID_UNKNOWN) { - spatialRef = SpatialReference.create(firstWKID); - } } else if (firstWKID != GeometryUtils.getWKID(geomref)) { LogUtils.Log_SRIDMismatch(LOG, geomrefs[0], geomref); return null; } } - // now build geometry array to pass to GeometryEngine.union - Geometry[] geomsToUnion = new Geometry[geomrefs.length]; + // build geometry list to pass to UnaryUnionOp + List geomsToUnion = new ArrayList<>(geomrefs.length); for (int i = 0; i < geomrefs.length; i++) { - //HiveGeometry hiveGeometry = GeometryUtils.geometryFromEsriShape(geomrefs[i]); - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomrefs[i]); + Geometry geom = GeometryUtils.geometryFromEsriShape(geomrefs[i]); - // if (i==0){ // get from ogcGeometry rather than re-create above? - // spatialRef = hiveGeometry.spatialReference; - // } - - if (ogcGeometry == null) { + if (geom == null) { LogUtils.Log_ArgumentsNull(LOG); return null; } - geomsToUnion[i] = ogcGeometry.getEsriGeometry(); + geomsToUnion.add(geom); } try { - Geometry unioned = GeometryEngine.union(geomsToUnion, spatialRef); + Geometry unioned = UnaryUnionOp.union(geomsToUnion); - // we have to infer the type of the differenced geometry because we don't know + // we have to infer the type of the unioned geometry because we don't know // if it's going to end up as a single or multi-part geometry - OGCType inferredType = GeometryUtils.getInferredOGCType(unioned); - - return GeometryUtils.geometryToEsriShapeBytesWritable(unioned, firstWKID, inferredType); + return GeometryUtils.geometryToEsriShapeBytesWritable(unioned, firstWKID); } catch (Exception e) { - LogUtils.Log_ExceptionThrown(LOG, "GeometryEngine.union", e); + LogUtils.Log_ExceptionThrown(LOG, "ST_Union", e); return null; } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Within.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Within.java index 8544f8fd69a5..49f04335fd0c 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Within.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Within.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.OperatorSimpleRelation; -import com.esri.core.geometry.OperatorWithin; import org.apache.hadoop.hive.ql.exec.Description; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; @Description(name = "ST_Within", value = "_FUNC_(geometry1, geometry2) - return true if geometry1 is within geometry2", @@ -30,8 +30,13 @@ public class ST_Within extends ST_GeometryRelational { @Override - protected OperatorSimpleRelation getRelationOperator() { - return OperatorWithin.local(); + protected boolean executeRelation(Geometry geom1, Geometry geom2) { + return geom1.within(geom2); + } + + @Override + protected boolean executeRelationPrepared(PreparedGeometry prepGeom1, Geometry geom2) { + return prepGeom1.within(geom2); } @Override diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_X.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_X.java index d3a75208c81a..ddf425971fab 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_X.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_X.java @@ -17,11 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPoint; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,15 +55,14 @@ public DoubleWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { return null; } switch (GeometryUtils.getType(geomref)) { case ST_POINT: - OGCPoint pt = (OGCPoint) ogcGeometry; - resultDouble.set(pt.X()); + resultDouble.set(((Point) geom).getX()); return resultDouble; default: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.getType(geomref)); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Y.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Y.java index e748557e5dc5..2ceddfe7b374 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Y.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Y.java @@ -17,11 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPoint; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,15 +55,14 @@ public DoubleWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { return null; } switch (GeometryUtils.getType(geomref)) { case ST_POINT: - OGCPoint pt = (OGCPoint) ogcGeometry; - resultDouble.set(pt.Y()); + resultDouble.set(((Point) geom).getY()); return resultDouble; default: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.getType(geomref)); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Z.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Z.java index 58a6252a4800..3dfcb111f854 100755 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Z.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Z.java @@ -17,11 +17,11 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCPoint; import org.apache.hadoop.hive.ql.exec.Description; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,19 +63,19 @@ public DoubleWritable evaluate(BytesWritable geomref) { return null; } - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref); - if (ogcGeometry == null) { - return null; - } - if (!ogcGeometry.is3D()) { - LogUtils.Log_Not3D(LOG); + Geometry geom = GeometryUtils.geometryFromEsriShape(geomref); + if (geom == null) { return null; } switch (GeometryUtils.getType(geomref)) { case ST_POINT: - OGCPoint pt = (OGCPoint) ogcGeometry; - resultDouble.set(pt.Z()); + double z = ((Point) geom).getCoordinate().getZ(); + if (Double.isNaN(z)) { + LogUtils.Log_Not3D(LOG); + return null; + } + resultDouble.set(z); return resultDouble; default: LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, GeometryUtils.getType(geomref)); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/BaseJsonSerDe.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/BaseJsonSerDe.java index 76df357d4bef..b0390921bef3 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/BaseJsonSerDe.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/BaseJsonSerDe.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.ql.udf.esri.serde; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils; import org.apache.hadoop.hive.ql.udf.esri.shims.HiveShims; +import org.locationtech.jts.geom.Geometry; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonGenerator; @@ -171,8 +171,8 @@ public Object deserialize(Writable json_in) throws SerDeException { if ("geometry".equals(parser.getCurrentName())) { if (geometryColumn > -1) { // create geometry and insert into geometry field - OGCGeometry ogcGeom = parseGeom(parser); - row.set(geometryColumn, ogcGeom == null ? null : GeometryUtils.geometryToEsriShapeBytesWritable(ogcGeom)); + Geometry geom = parseGeom(parser); + row.set(geometryColumn, geom == null ? null : GeometryUtils.geometryToEsriShapeBytesWritable(geom)); } else { // no geometry in select field set, don't even bother parsing parser.skipChildren(); @@ -275,8 +275,8 @@ public Writable serialize(Object obj, ObjectInspector oi) throws SerDeException bytesWritable = (BytesWritable) got; else // SparkSQL, #97 bytesWritable = new BytesWritable((byte[]) got); // idea: avoid extra object - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(bytesWritable); - jsonGen.writeRaw(",\"geometry\":" + outGeom(ogcGeometry)); + Geometry geometry = GeometryUtils.geometryFromEsriShape(bytesWritable); + jsonGen.writeRaw(",\"geometry\":" + outGeom(geometry)); } } @@ -335,11 +335,11 @@ private void generateJsonFromWritable(Writable value, int fieldIndex, String lab } } - // Write OGCGeometry to JSON - abstract protected String outGeom(OGCGeometry geom); + // Write JTS Geometry to JSON string + abstract protected String outGeom(Geometry geom); - // Parse OGCGeometry from JSON - abstract protected OGCGeometry parseGeom(JsonParser parser); + // Parse JTS Geometry from JSON + abstract protected Geometry parseGeom(JsonParser parser); private java.sql.Date parseDate(JsonParser parser) throws IOException { java.sql.Date jsd = null; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/EsriJsonSerDe.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/EsriJsonSerDe.java index afa692d93ccb..3426c24a6c60 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/EsriJsonSerDe.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/EsriJsonSerDe.java @@ -17,147 +17,35 @@ */ package org.apache.hadoop.hive.ql.udf.esri.serde; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.JsonGeometryException; -import com.esri.core.geometry.JsonReader; -import com.esri.core.geometry.MapGeometry; -import com.esri.core.geometry.OperatorImportFromJson; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.hadoop.hive.ql.udf.esri.EsriJsonConverter; +import org.locationtech.jts.geom.Geometry; public class EsriJsonSerDe extends BaseJsonSerDe { @Override - protected String outGeom(OGCGeometry geom) { - return geom.asJson(); - } - - @Override - protected OGCGeometry parseGeom(JsonParser parser) { - MapGeometry mapGeom = - OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new HiveJsonParserReader(parser)); - return OGCGeometry.createFromEsriGeometry(mapGeom.getGeometry(), mapGeom.getSpatialReference()); - } -} - -class HiveJsonParserReader implements JsonReader { - private JsonParser m_jsonParser; - - public HiveJsonParserReader(JsonParser jsonParser) { - this.m_jsonParser = jsonParser; - } - - public static JsonReader createFromString(String str) { + protected String outGeom(Geometry geom) { try { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParser = factory.createParser(str); - jsonParser.nextToken(); - return new com.esri.core.geometry.JsonParserReader(jsonParser); - } catch (Exception var3) { - throw new JsonGeometryException(var3.getMessage()); + int wkid = geom.getSRID(); + return EsriJsonConverter.geometryToEsriJson(geom, wkid); + } catch (Exception e) { + return "null"; } } - public static JsonReader createFromStringNNT(String str) { - try { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParser = factory.createParser(str); - return new com.esri.core.geometry.JsonParserReader(jsonParser); - } catch (Exception var3) { - throw new JsonGeometryException(var3.getMessage()); - } - } - - private static Token mapToken(JsonToken token) { - if (token == JsonToken.END_ARRAY) { - return Token.END_ARRAY; - } else if (token == JsonToken.END_OBJECT) { - return Token.END_OBJECT; - } else if (token == JsonToken.FIELD_NAME) { - return Token.FIELD_NAME; - } else if (token == JsonToken.START_ARRAY) { - return Token.START_ARRAY; - } else if (token == JsonToken.START_OBJECT) { - return Token.START_OBJECT; - } else if (token == JsonToken.VALUE_FALSE) { - return Token.VALUE_FALSE; - } else if (token == JsonToken.VALUE_NULL) { - return Token.VALUE_NULL; - } else if (token == JsonToken.VALUE_NUMBER_FLOAT) { - return Token.VALUE_NUMBER_FLOAT; - } else if (token == JsonToken.VALUE_NUMBER_INT) { - return Token.VALUE_NUMBER_INT; - } else if (token == JsonToken.VALUE_STRING) { - return Token.VALUE_STRING; - } else if (token == JsonToken.VALUE_TRUE) { - return Token.VALUE_TRUE; - } else if (token == null) { + @Override + protected Geometry parseGeom(JsonParser parser) { + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(parser); + if (node == null) { + return null; + } + return EsriJsonConverter.esriJsonToGeometry(node.toString()); + } catch (Exception e) { return null; - } else { - throw new JsonGeometryException("unexpected token"); - } - } - - public Token nextToken() throws JsonGeometryException { - try { - JsonToken token = this.m_jsonParser.nextToken(); - return mapToken(token); - } catch (Exception var2) { - throw new JsonGeometryException(var2); - } - } - - public Token currentToken() throws JsonGeometryException { - try { - return mapToken(this.m_jsonParser.getCurrentToken()); - } catch (Exception var2) { - throw new JsonGeometryException(var2); - } - } - - public void skipChildren() throws JsonGeometryException { - try { - this.m_jsonParser.skipChildren(); - } catch (Exception var2) { - throw new JsonGeometryException(var2); - } - } - - public String currentString() throws JsonGeometryException { - try { - return this.m_jsonParser.getText(); - } catch (Exception var2) { - throw new JsonGeometryException(var2); - } - } - - public double currentDoubleValue() throws JsonGeometryException { - try { - return this.m_jsonParser.getValueAsDouble(); - } catch (Exception var2) { - throw new JsonGeometryException(var2); - } - } - - public int currentIntValue() throws JsonGeometryException { - try { - return this.m_jsonParser.getValueAsInt(); - } catch (Exception var2) { - throw new JsonGeometryException(var2); - } - } - - public boolean currentBooleanValue() { - Token t = this.currentToken(); - if (t == Token.VALUE_TRUE) { - return true; - } else if (t == Token.VALUE_FALSE) { - return false; - } else { - throw new JsonGeometryException("Not a boolean"); } } } - diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/GeoJsonSerDe.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/GeoJsonSerDe.java index a21aa69f46e6..2043fa393e5a 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/GeoJsonSerDe.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/GeoJsonSerDe.java @@ -17,11 +17,13 @@ */ package org.apache.hadoop.hive.ql.udf.esri.serde; -import com.esri.core.geometry.ogc.OGCGeometry; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.ParseException; import java.io.IOException; @@ -36,20 +38,18 @@ public GeoJsonSerDe() { } @Override - protected String outGeom(OGCGeometry geom) { - return geom.asGeoJson(); + protected String outGeom(Geometry geom) { + return GeometryUtils.geoJsonWriter().write(geom); } @Override - protected OGCGeometry parseGeom(JsonParser parser) { + protected Geometry parseGeom(JsonParser parser) { try { ObjectNode node = mapper.readTree(parser); - return OGCGeometry.fromGeoJson(node.toString()); - } catch (JsonProcessingException e1) { - e1.printStackTrace(); // TODO Auto-generated catch block - } catch (IOException e1) { - e1.printStackTrace(); // TODO Auto-generated catch block + return GeometryUtils.geoJsonReader().read(node.toString()); + } catch (ParseException | IOException e1) { + LOG.error("Error parsing GeoJSON", e1); } - return null; // ? + return null; } } diff --git a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStCentroid.java b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStCentroid.java index fdfffc876aff..722ac7a6a64b 100644 --- a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStCentroid.java +++ b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStCentroid.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Point; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; @@ -33,25 +32,30 @@ public class TestStCentroid { /** * Validates the centroid geometry writable. * - * @param point - * the represented point location. + * @param expectedX + * the expected X (longitude) of the centroid. + * @param expectedY + * the expected Y (latitude) of the centroid. * @param geometryAsWritable * the geometry represented as {@link BytesWritable}. */ - private static void validatePoint(Point point, BytesWritable geometryAsWritable) { + private static void validatePoint(double expectedX, double expectedY, BytesWritable geometryAsWritable) { ST_X getX = new ST_X(); ST_Y getY = new ST_Y(); DoubleWritable xAsWritable = getX.evaluate(geometryAsWritable); DoubleWritable yAsWritable = getY.evaluate(geometryAsWritable); - if (null == xAsWritable || null == yAsWritable || Math.abs(point.getX() - xAsWritable.get()) > Epsilon - || Math.abs(point.getY() - yAsWritable.get()) > Epsilon) - System.err.println("validateCentroid: " + (new ST_AsText()).evaluate(geometryAsWritable) + " ~ " + point); + if (null == xAsWritable || null == yAsWritable || Math.abs(expectedX - xAsWritable.get()) > Epsilon + || Math.abs(expectedY - yAsWritable.get()) > Epsilon) { + System.err.println( + "validateCentroid: " + (new ST_AsText()).evaluate(geometryAsWritable) + " ~ (" + expectedX + ", " + expectedY + + ")"); + } assertNotNull("The x writable must not be null!", xAsWritable); assertNotNull("The y writable must not be null!", yAsWritable); - assertEquals("Longitude is different!", point.getX(), xAsWritable.get(), Epsilon); - assertEquals("Latitude is different!", point.getY(), yAsWritable.get(), Epsilon); + assertEquals("Longitude is different!", expectedX, xAsWritable.get(), Epsilon); + assertEquals("Latitude is different!", expectedY, yAsWritable.get(), Epsilon); } @Test @@ -60,7 +64,7 @@ public void TestSimplePointCentroid() throws Exception { final ST_Point stPt = new ST_Point(); BytesWritable bwGeom = stPt.evaluate(new Text("point (2 3)")); BytesWritable bwCentroid = stCtr.evaluate(bwGeom); - validatePoint(new Point(2, 3), bwCentroid); + validatePoint(2, 3, bwCentroid); } @Test @@ -69,7 +73,7 @@ public void TestMultiPointCentroid() throws Exception { final ST_MultiPoint stMp = new ST_MultiPoint(); BytesWritable bwGeom = stMp.evaluate(new Text("multipoint ((0 0), (1 1), (1 -1), (6 0))")); BytesWritable bwCentroid = stCtr.evaluate(bwGeom); - validatePoint(new Point(2, 0), bwCentroid); + validatePoint(2, 0, bwCentroid); } @Test @@ -78,12 +82,12 @@ public void TestLineCentroid() throws Exception { final ST_LineString stLn = new ST_LineString(); BytesWritable bwGeom = stLn.evaluate(new Text("linestring (0 0, 6 0)")); BytesWritable bwCentroid = stCtr.evaluate(bwGeom); - validatePoint(new Point(3, 0), bwCentroid); + validatePoint(3, 0, bwCentroid); bwGeom = stLn.evaluate(new Text("linestring (0 0, 0 4, 12 4)")); bwCentroid = stCtr.evaluate(bwGeom); // L1 = 4, L2 = 12, W1 = 0.25, W2 = 0.75, X = W1 * 0 + W2 * 6, Y = W1 * 2 + W2 * 4 // Or like centroid of multipoint of 1 of (0 2) and 3 of (6 4) - validatePoint(new Point(4.5, 3.5), bwCentroid); + validatePoint(4.5, 3.5, bwCentroid); } @Test @@ -92,13 +96,13 @@ public void TestPolygonCentroid() throws Exception { final ST_Polygon stPoly = new ST_Polygon(); BytesWritable bwGeom = stPoly.evaluate(new Text("polygon ((0 0, 0 8, 8 8, 8 0, 0 0))")); BytesWritable bwCentroid = stCtr.evaluate(bwGeom); - validatePoint(new Point(4, 4), bwCentroid); + validatePoint(4, 4, bwCentroid); bwGeom = stPoly.evaluate(new Text("polygon ((1 1, 5 1, 3 4, 1 1))")); bwCentroid = stCtr.evaluate(bwGeom); - validatePoint(new Point(3, 2), bwCentroid); + validatePoint(3, 2, bwCentroid); bwGeom = stPoly.evaluate(new Text("polygon ((14 0, -14 0, -2 24, 2 24, 14 0))")); bwCentroid = stCtr.evaluate(bwGeom); // Cross-checked with ... - validatePoint(new Point(0, 9), bwCentroid); // ... omnicalculator.com/math/centroid + validatePoint(0, 9, bwCentroid); // ... omnicalculator.com/math/centroid } } diff --git a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStGeomFromShape.java b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStGeomFromShape.java index 659f1bb97bfd..66ad4f0782d2 100644 --- a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStGeomFromShape.java +++ b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStGeomFromShape.java @@ -17,17 +17,15 @@ */ package org.apache.hadoop.hive.ql.udf.esri; -import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.Point; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.serde2.io.DoubleWritable; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.IntWritable; +import org.locationtech.jts.geom.Geometry; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -166,13 +164,16 @@ public void testGeomFromLineShape() throws UDFArgumentException { BytesWritable geometryAsWritable = fromShape.evaluate(shapeAsWritable, wkid); assertNotNull("The geometry writable must not be null!", geometryAsWritable); - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geometryAsWritable); - assertNotNull("The OGC geometry must not be null!", ogcGeometry); - - Geometry esriGeometry = ogcGeometry.getEsriGeometry(); - assertNotNull("The Esri geometry must not be null!", esriGeometry); - assertTrue("The geometries are different!", - GeometryEngine.equals(line, esriGeometry, SpatialReference.create(wkid))); + Geometry jtsGeometry = GeometryUtils.geometryFromEsriShape(geometryAsWritable); + assertNotNull("The JTS geometry must not be null!", jtsGeometry); + assertEquals("The SRID is different!", wkid, jtsGeometry.getSRID()); + assertEquals("Expected 2 coordinates (line start/end)!", 2, jtsGeometry.getNumPoints()); + assertEquals("Start X differs!", createFirstLocation().getX(), jtsGeometry.getCoordinates()[0].x, Epsilon); + assertEquals("Start Y differs!", createFirstLocation().getY(), jtsGeometry.getCoordinates()[0].y, Epsilon); + assertEquals("End X differs!", createSecondLocation().getX(), + jtsGeometry.getCoordinates()[jtsGeometry.getNumPoints() - 1].x, Epsilon); + assertEquals("End Y differs!", createSecondLocation().getY(), + jtsGeometry.getCoordinates()[jtsGeometry.getNumPoints() - 1].y, Epsilon); } @Test @@ -189,13 +190,16 @@ public void testGeomFromPolylineShape() throws UDFArgumentException { BytesWritable geometryAsWritable = fromShape.evaluate(shapeAsWritable, wkid); assertNotNull("The geometry writable must not be null!", geometryAsWritable); - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geometryAsWritable); - assertNotNull("The OGC geometry must not be null!", ogcGeometry); - - Geometry esriGeometry = ogcGeometry.getEsriGeometry(); - assertNotNull("The Esri geometry must not be null!", esriGeometry); - assertTrue("The geometries are different!", - GeometryEngine.equals(line, esriGeometry, SpatialReference.create(wkid))); + Geometry jtsGeometry = GeometryUtils.geometryFromEsriShape(geometryAsWritable); + assertNotNull("The JTS geometry must not be null!", jtsGeometry); + assertEquals("The SRID is different!", wkid, jtsGeometry.getSRID()); + assertEquals("Expected 4 points in polyline!", 4, jtsGeometry.getNumPoints()); + assertEquals("Start X differs!", createFirstLocation().getX(), jtsGeometry.getCoordinates()[0].x, Epsilon); + assertEquals("Start Y differs!", createFirstLocation().getY(), jtsGeometry.getCoordinates()[0].y, Epsilon); + assertEquals("End X differs!", createFourthLocation().getX(), + jtsGeometry.getCoordinates()[jtsGeometry.getNumPoints() - 1].x, Epsilon); + assertEquals("End Y differs!", createFourthLocation().getY(), + jtsGeometry.getCoordinates()[jtsGeometry.getNumPoints() - 1].y, Epsilon); } @Test @@ -212,12 +216,12 @@ public void testGeomFromPolygonShape() throws UDFArgumentException { BytesWritable geometryAsWritable = fromShape.evaluate(shapeAsWritable, wkid); assertNotNull("The geometry writable must not be null!", geometryAsWritable); - OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geometryAsWritable); - assertNotNull("The OGC geometry must not be null!", ogcGeometry); - - Geometry esriGeometry = ogcGeometry.getEsriGeometry(); - assertNotNull("The Esri geometry must not be null!", esriGeometry); - assertTrue("The geometries are different!", - GeometryEngine.equals(polygon, esriGeometry, SpatialReference.create(wkid))); + Geometry jtsGeometry = GeometryUtils.geometryFromEsriShape(geometryAsWritable); + assertNotNull("The JTS geometry must not be null!", jtsGeometry); + assertEquals("The SRID is different!", wkid, jtsGeometry.getSRID()); + assertTrue("Expected a polygon geometry!", jtsGeometry instanceof org.locationtech.jts.geom.Polygon); + // Polygon has 4 vertices + closing vertex = 5 points in the exterior ring + assertEquals("Expected 5 coordinates in polygon ring!", 5, + ((org.locationtech.jts.geom.Polygon) jtsGeometry).getExteriorRing().getNumPoints()); } } diff --git a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/JsonSerDeTestingBase.java b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/JsonSerDeTestingBase.java index 1cf4b1253be4..a229d035a8e9 100644 --- a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/JsonSerDeTestingBase.java +++ b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/JsonSerDeTestingBase.java @@ -17,12 +17,10 @@ */ package org.apache.hadoop.hive.ql.udf.esri.serde; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.MapGeometry; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.ogc.OGCGeometry; import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils; + +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.apache.hadoop.hive.serde2.AbstractSerDe; import org.apache.hadoop.hive.serde2.io.ByteWritable; import org.apache.hadoop.hive.serde2.io.DateWritable; @@ -100,19 +98,16 @@ protected void addWritable(ArrayList stuff, java.sql.Timestamp item) { } protected void addWritable(ArrayList stuff, Geometry geom) { - addWritable(stuff, geom, null); - } - - protected void addWritable(ArrayList stuff, MapGeometry geom) { - addWritable(stuff, geom.getGeometry(), geom.getSpatialReference()); - } - - protected void addWritable(ArrayList stuff, Geometry geom, SpatialReference sref) { - stuff.add(GeometryUtils.geometryToEsriShapeBytesWritable(OGCGeometry.createFromEsriGeometry(geom, sref))); + stuff.add(GeometryUtils.geometryToEsriShapeBytesWritable(geom)); } protected void ckPoint(Point refPt, BytesWritable fieldData) { - Assert.assertEquals(refPt, GeometryUtils.geometryFromEsriShape(fieldData).getEsriGeometry()); + Geometry geom = GeometryUtils.geometryFromEsriShape(fieldData); + Assert.assertNotNull("Geometry must not be null!", geom); + Assert.assertTrue("Expected a Point geometry!", geom instanceof Point); + Point actualPt = (Point) geom; + Assert.assertEquals("X coordinate differs!", refPt.getX(), actualPt.getX(), 0.0001); + Assert.assertEquals("Y coordinate differs!", refPt.getY(), actualPt.getY(), 0.0001); } protected long epochFromWritable(Object dwHive) throws Exception { diff --git a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestEsriJsonSerDe.java b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestEsriJsonSerDe.java index 30e5eebfcf44..a0d02f02428b 100644 --- a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestEsriJsonSerDe.java +++ b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestEsriJsonSerDe.java @@ -17,11 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri.serde; -import com.esri.core.geometry.Point; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils; import org.apache.hadoop.hive.ql.udf.esri.shims.HiveShims; +import org.locationtech.jts.geom.Coordinate; import org.apache.hadoop.hive.serde2.AbstractSerDe; import org.apache.hadoop.hive.serde2.io.ByteWritable; import org.apache.hadoop.hive.serde2.io.ShortWritable; @@ -125,7 +126,7 @@ public void TestPointWrite() throws Exception { StructObjectInspector rowOI = (StructObjectInspector) jserde.getObjectInspector(); // {"attributes":{},"geometry":{"x":15.0,"y":5.0}} - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Writable jsw = jserde.serialize(stuff, rowOI); String rslt = jsw.toString(); JsonNode jn = new ObjectMapper().readTree(rslt); @@ -291,13 +292,13 @@ public void TestPointParse() throws Exception { Object row = jserde.deserialize(value); StructField f0 = rowOI.getStructFieldRef("shape"); Object fieldData = rowOI.getStructFieldData(row, f0); - ckPoint(new Point(15.0, 5.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0)), (BytesWritable) fieldData); value.set("{\"attributes\":{},\"geometry\":{\"x\":7.0,\"y\":4.0}}"); row = jserde.deserialize(value); f0 = rowOI.getStructFieldRef("shape"); fieldData = rowOI.getStructFieldData(row, f0); - ckPoint(new Point(7.0, 4.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 4.0)), (BytesWritable) fieldData); } @Test @@ -331,17 +332,17 @@ public void TestPointOnly() throws Exception { StructObjectInspector rowOI = (StructObjectInspector) jserde.getObjectInspector(); //value.set("{\"attributes\":{},\"geometry\":{\"x\":15.0,\"y\":5.0}}"); - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Object row = runSerDe(stuff, jserde, rowOI); Object fieldData = getField("shape", row, rowOI); - ckPoint(new Point(15.0, 5.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0)), (BytesWritable) fieldData); //value.set("{\"attributes\":{},\"geometry\":{\"x\":7.0,\"y\":4.0}}"); stuff.clear(); - addWritable(stuff, new Point(7.0, 4.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 4.0))); row = runSerDe(stuff, jserde, rowOI); fieldData = getField("shape", row, rowOI); - ckPoint(new Point(7.0, 4.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 4.0)), (BytesWritable) fieldData); } @Test @@ -355,7 +356,7 @@ public void TestIntPoint() throws Exception { //value.set("{\"attributes\":{\"num\":7},\"geometry\":{\"x\":15.0,\"y\":5.0}}"); addWritable(stuff, 7L); - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Object row = runSerDe(stuff, jserde, rowOI); Object fieldData = getField("num", row, rowOI); Assert.assertEquals(7, ((LongWritable) fieldData).get()); @@ -363,12 +364,12 @@ public void TestIntPoint() throws Exception { //value.set("{\"attributes\":{\"num\":4},\"geometry\":{\"x\":7.0,\"y\":2.0}}"); stuff.clear(); addWritable(stuff, 4L); - addWritable(stuff, new Point(7.0, 2.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 2.0))); row = runSerDe(stuff, jserde, rowOI); fieldData = getField("num", row, rowOI); Assert.assertEquals(4, ((LongWritable) fieldData).get()); fieldData = getField("shape", row, rowOI); - ckPoint(new Point(7.0, 2.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 2.0)), (BytesWritable) fieldData); } @Test @@ -402,10 +403,10 @@ public void TestNullGeom() throws Exception { StructObjectInspector rowOI = (StructObjectInspector) jserde.getObjectInspector(); //value.set("{\"attributes\":{},\"geometry\":{\"x\":15.0,\"y\":5.0}}"); - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Object row = runSerDe(stuff, jserde, rowOI); Object fieldData = getField("shape", row, rowOI); - ckPoint(new Point(15.0, 5.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0)), (BytesWritable) fieldData); //value.set("{\"attributes\":{},\"geometry\":null}"); stuff.set(0, null); diff --git a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestGeoJsonSerDe.java b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestGeoJsonSerDe.java index 4f137ad321f1..8d05313d943e 100644 --- a/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestGeoJsonSerDe.java +++ b/ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestGeoJsonSerDe.java @@ -17,11 +17,12 @@ */ package org.apache.hadoop.hive.ql.udf.esri.serde; -import com.esri.core.geometry.Point; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.ql.udf.esri.GeometryUtils; import org.apache.hadoop.hive.ql.udf.esri.shims.HiveShims; +import org.locationtech.jts.geom.Coordinate; import org.apache.hadoop.hive.serde2.AbstractSerDe; import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; @@ -96,7 +97,7 @@ public void TestPointWrite() throws Exception { StructObjectInspector rowOI = (StructObjectInspector) jserde.getObjectInspector(); // {"properties":{},"geometry":{"type":"Point","coordinates":[15.0,5.0]}} - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Writable jsw = jserde.serialize(stuff, rowOI); String rslt = jsw.toString(); JsonNode jn = new ObjectMapper().readTree(rslt); @@ -200,13 +201,13 @@ public void TestPointParse() throws Exception { Object row = jserde.deserialize(value); StructField f0 = rowOI.getStructFieldRef("shape"); Object fieldData = rowOI.getStructFieldData(row, f0); - ckPoint(new Point(15.0, 5.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0)), (BytesWritable) fieldData); value.set("{\"properties\":{},\"geometry\":{\"type\":\"Point\",\"type\":\"Point\",\"coordinates\":[7.0,4.0]}}"); row = jserde.deserialize(value); f0 = rowOI.getStructFieldRef("shape"); fieldData = rowOI.getStructFieldData(row, f0); - ckPoint(new Point(7.0, 4.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 4.0)), (BytesWritable) fieldData); } @Test @@ -240,17 +241,17 @@ public void TestPointOnly() throws Exception { StructObjectInspector rowOI = (StructObjectInspector) jserde.getObjectInspector(); //value.set("{\"properties\":{},\"geometry\":{\"type\":\"Point\",\"coordinates\":[15.0,5.0]}}"); - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Object row = runSerDe(stuff, jserde, rowOI); Object fieldData = getField("shape", row, rowOI); - ckPoint(new Point(15.0, 5.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0)), (BytesWritable) fieldData); //value.set("{\"properties\":{},\"geometry\":{\"type\":\"Point\",\"type\":\"Point\",\"coordinates\":[7.0,4.0]}}"); stuff.clear(); - addWritable(stuff, new Point(7.0, 4.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 4.0))); row = runSerDe(stuff, jserde, rowOI); fieldData = getField("shape", row, rowOI); - ckPoint(new Point(7.0, 4.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 4.0)), (BytesWritable) fieldData); } @Test @@ -264,7 +265,7 @@ public void TestIntPoint() throws Exception { // value.set("{\"properties\":{\"num\":7},\"geometry\":{\"type\":\"Point\",\"coordinates\":[15.0,5.0]}}"); addWritable(stuff, 7L); - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Object row = runSerDe(stuff, jserde, rowOI); Object fieldData = getField("num", row, rowOI); Assert.assertEquals(7, ((LongWritable) fieldData).get()); @@ -272,12 +273,12 @@ public void TestIntPoint() throws Exception { //value.set("{\"properties\":{\"num\":4},\"geometry\":{\"type\":\"Point\",\"coordinates\":[7.0,2.0]}}"); stuff.clear(); addWritable(stuff, 4L); - addWritable(stuff, new Point(7.0, 2.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 2.0))); row = runSerDe(stuff, jserde, rowOI); fieldData = getField("num", row, rowOI); Assert.assertEquals(4, ((LongWritable) fieldData).get()); fieldData = getField("shape", row, rowOI); - ckPoint(new Point(7.0, 2.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(7.0, 2.0)), (BytesWritable) fieldData); } @Test @@ -311,10 +312,10 @@ public void TestNullGeom() throws Exception { StructObjectInspector rowOI = (StructObjectInspector) jserde.getObjectInspector(); //value.set("{\"properties\":{},\"geometry\":{\"type\":\"Point\",\"coordinates\":[15.0,5.0]}}"); - addWritable(stuff, new Point(15.0, 5.0)); + addWritable(stuff, GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0))); Object row = runSerDe(stuff, jserde, rowOI); Object fieldData = getField("shape", row, rowOI); - ckPoint(new Point(15.0, 5.0), (BytesWritable) fieldData); + ckPoint(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(15.0, 5.0)), (BytesWritable) fieldData); //value.set("{\"properties\":{},\"coordinates\":null}"); stuff.set(0, null); diff --git a/ql/src/test/queries/clientpositive/geospatial_udfs.q b/ql/src/test/queries/clientpositive/geospatial_udfs.q index 29df7aa9a6a4..d4a2d46f3b6c 100644 --- a/ql/src/test/queries/clientpositive/geospatial_udfs.q +++ b/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. create table geom_binary ( diff --git a/ql/src/test/results/clientpositive/llap/geospatial_udfs.q.out b/ql/src/test/results/clientpositive/llap/geospatial_udfs.q.out index 2a6c1e3a34fd..1d1589ead2c2 100644 --- a/ql/src/test/results/clientpositive/llap/geospatial_udfs.q.out +++ b/ql/src/test/results/clientpositive/llap/geospatial_udfs.q.out @@ -94,30 +94,30 @@ order by id POSTHOOK: type: QUERY POSTHOOK: Input: default@geom_binary #### A masked pattern was here #### -1 MULTIPOLYGON (((0 0, 1 0, 0 1, 0 0)), ((2 2, 3 2, 2 3, 2 2))) ST_MULTIPOLYGON 3.0 3.0 NULL 0.0 0.0 NULL 2 POINT (1.3333333333333333 1.3333333333333333) 2 false false true -2 MULTILINESTRING ((2 4, 10 10), (20 20, 7 8)) ST_MULTILINESTRING 20.0 20.0 NULL 2.0 4.0 NULL 2 POINT (10.791617601072488 11.472176427667655) 1 false false true +1 MULTIPOLYGON (((0 0, 1 0, 0 1, 0 0)), ((2 2, 3 2, 2 3, 2 2))) ST_MULTIPOLYGON 3.0 3.0 NULL 0.0 0.0 NULL 2 POINT (1.333333333333 1.333333333333) 2 false false true +2 MULTILINESTRING ((2 4, 10 10), (20 20, 7 8)) ST_MULTILINESTRING 20.0 20.0 NULL 2.0 4.0 NULL 2 POINT (10.791617601072 11.472176427667) 1 false false true 3 MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) ST_MULTIPOINT 40.0 40.0 NULL 10.0 10.0 NULL 4 POINT (25 25) 0 false false true -4 POLYGON ((0 0, 10 10, 0 10, 0 0)) ST_POLYGON 10.0 10.0 NULL 0.0 0.0 NULL NULL POINT (3.3333333333333335 6.666666666666667) 2 false false true +4 POLYGON ((0 0, 10 10, 0 10, 0 0)) ST_POLYGON 10.0 10.0 NULL 0.0 0.0 NULL NULL POINT (3.333333333333 6.666666666666) 2 false false true 6 LINESTRING (10 10, 20 20) ST_LINESTRING 20.0 20.0 NULL 10.0 10.0 NULL NULL POINT (15 15) 1 false false true 7 POINT (10.02 20.01) ST_POINT 10.02 20.01 NULL 10.02 20.01 NULL NULL POINT (10.02 20.01) 0 false false true 8 LINESTRING Z (1.5 2.5 2, 3 2.2 1) ST_LINESTRING 3.0 2.5 2.0 1.5 2.2 1.0 NULL POINT (2.25 2.35) 1 false false true 9 MULTIPOINT Z ((0 0 1), (2 2 3)) ST_MULTIPOINT 2.0 2.0 3.0 0.0 0.0 1.0 2 POINT (1 1) 0 false false true 10 POINT Z (10.02 20.01 25) ST_POINT 10.02 20.01 25.0 10.02 20.01 25.0 NULL POINT (10.02 20.01) 0 false false true 11 MULTIPOLYGON (((0 0, 1 0, 0 1, 0 0)), ((2 2, 1 2, 2 1, 2 2))) ST_MULTIPOLYGON 2.0 2.0 NULL 0.0 0.0 NULL 2 POINT (1 1) 2 false false true -12 POLYGON ((0 0, 8 0, 0 8, 0 0), (1 1, 1 5, 5 1, 1 1)) ST_POLYGON 8.0 8.0 NULL 0.0 0.0 NULL NULL POINT (2.7777777777777777 2.7777777777777777) 2 false false true +12 POLYGON ((0 0, 8 0, 0 8, 0 0), (1 1, 1 5, 5 1, 1 1)) ST_POLYGON 8.0 8.0 NULL 0.0 0.0 NULL NULL POINT (2.777777777777 2.777777777777) 2 false false true 13 POINT Z (1.5 2.5 2) ST_POINT 1.5 2.5 2.0 1.5 2.5 2.0 NULL POINT (1.5 2.5) 0 false false true 14 POINT (5 6) ST_POINT 5.0 6.0 NULL 5.0 6.0 NULL NULL POINT (5 6) 0 false false true 15 POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1)) ST_POLYGON 4.0 4.0 NULL 1.0 1.0 NULL NULL POINT (2.5 2.5) 2 false false true 16 LINESTRING (10 10, 20 20) ST_LINESTRING 20.0 20.0 NULL 10.0 10.0 NULL NULL POINT (15 15) 1 false false true 17 LINESTRING (0 0, 3 4, 0 4, 0 0) ST_LINESTRING 3.0 4.0 NULL 0.0 0.0 NULL NULL POINT (1 2.5) 1 false false true -18 MULTILINESTRING ((0 0, 3 4, 2 2, 0 0), (6 2, 7 5, 6 8, 6 2)) ST_MULTILINESTRING 7.0 8.0 NULL 0.0 0.0 NULL 2 POINT (4.155076025772472 3.624959796288092) 1 false false true +18 MULTILINESTRING ((0 0, 3 4, 2 2, 0 0), (6 2, 7 5, 6 8, 6 2)) ST_MULTILINESTRING 7.0 8.0 NULL 0.0 0.0 NULL 2 POINT (4.155076025772 3.624959796288) 1 false false true 19 POINT EMPTY ST_POINT NaN NaN NULL NaN NaN NULL NULL POINT EMPTY 0 true false true -20 LINESTRING (0 0, 1 0, 1 1, 0 2, 2 2, 1 1, 2 0) ST_LINESTRING 2.0 2.0 NULL 0.0 0.0 NULL NULL POINT (1.0251262658470837 1.1464466094067263) 1 false false false +20 LINESTRING (0 0, 1 0, 1 1, 0 2, 2 2, 1 1, 2 0) ST_LINESTRING 2.0 2.0 NULL 0.0 0.0 NULL NULL POINT (1.025126265847 1.146446609406) 1 false false false 21 POINT M (0 3 1) ST_POINT 0.0 3.0 NULL 0.0 3.0 NULL NULL POINT (0 3) 0 false true true 22 POINT ZM (0 3 1 2) ST_POINT 0.0 3.0 1.0 0.0 3.0 1.0 NULL POINT (0 3) 0 false true true 23 LINESTRING M (10 10 2, 20 20 4) ST_LINESTRING 20.0 20.0 NULL 10.0 10.0 NULL NULL POINT (15 15) 1 false true true 24 POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1)) ST_POLYGON 4.0 4.0 NULL 1.0 1.0 NULL NULL POINT (2.5 2.5) 2 false false true -25 POLYGON ((2 0, 3 1, 2 1, 2 0)) ST_POLYGON 3.0 1.0 NULL 2.0 0.0 NULL NULL POINT (2.3333333333333335 0.6666666666666666) 2 false false true +25 POLYGON ((2 0, 3 1, 2 1, 2 0)) ST_POLYGON 3.0 1.0 NULL 2.0 0.0 NULL NULL POINT (2.333333333333 0.666666666666) 2 false false true 26 POINT (0 0) ST_POINT 0.0 0.0 NULL 0.0 0.0 NULL NULL POINT (0 0) 0 false false true 27 LINESTRING (1 2, 3 4) ST_LINESTRING 3.0 4.0 NULL 1.0 2.0 NULL NULL POINT (2 3) 1 false false true 28 LINESTRING (20 20, 7 8) ST_LINESTRING 20.0 20.0 NULL 7.0 8.0 NULL NULL POINT (13.5 14) 1 false false true @@ -366,7 +366,7 @@ where id = 7 POSTHOOK: type: QUERY POSTHOOK: Input: default@geom_binary #### A masked pattern was here #### -POLYGON ((11.02 20.01, 11.017858923238602 20.075403129230146, 11.011444861373809 20.140526192220054, 11.00078528040323 20.20509032201613, 10.985925826289067 20.26881904510252, 10.966930129495104 20.331439465303163, 10.943879532511286 20.39268343236509, 10.916872741532687 20.452288690219003, 10.886025403784437 20.51, 10.851469612302544 20.565570233019603, 10.813353340291235 20.61876142900872, 10.771839807478976 20.66934581510007, 10.727106781186547 20.717106781186548, 10.679345815100069 20.761839807478978, 10.628761429008721 20.80335334029124, 10.5755702330196 20.841469612302546, 10.52 20.87602540378444, 10.462288690219001 20.90687274153269, 10.40268343236509 20.933879532511288, 10.34143946530316 20.956930129495106, 10.27881904510252 20.97592582628907, 10.215090322016128 20.99078528040323, 10.15052619222005 21.001444861373813, 10.085403129230142 21.007858923238604, 10.02 21.01, 9.954596870769857 21.007858923238604, 9.889473807779948 21.001444861373813, 9.824909677983872 20.99078528040323, 9.76118095489748 20.975925826289068, 9.698560534696838 20.956930129495106, 9.63731656763491 20.933879532511288, 9.577711309780998 20.906872741532688, 9.52 20.87602540378444, 9.464429766980398 20.841469612302546, 9.41123857099128 20.80335334029124, 9.36065418489993 20.761839807478978, 9.312893218813452 20.717106781186548, 9.268160192521023 20.66934581510007, 9.226646659708765 20.61876142900872, 9.188530387697455 20.565570233019603, 9.15397459621556 20.51, 9.123127258467312 20.452288690219003, 9.096120467488714 20.392683432365093, 9.073069870504893 20.331439465303163, 9.054074173710932 20.26881904510252, 9.03921471959677 20.20509032201613, 9.028555138626189 20.140526192220054, 9.022141076761397 20.075403129230146, 9.02 20.01, 9.022141076761397 19.944596870769857, 9.02855513862619 19.87947380777995, 9.03921471959677 19.814909677983874, 9.054074173710932 19.75118095489748, 9.073069870504895 19.68856053469684, 9.096120467488714 19.627316567634914, 9.123127258467312 19.567711309781, 9.153974596215562 19.51, 9.188530387697455 19.4544297669804, 9.226646659708765 19.401238570991282, 9.268160192521023 19.350654184899934, 9.312893218813452 19.302893218813455, 9.36065418489993 19.258160192521025, 9.411238570991278 19.216646659708765, 9.464429766980398 19.178530387697457, 9.52 19.143974596215564, 9.577711309780998 19.113127258467312, 9.63731656763491 19.086120467488715, 9.698560534696838 19.063069870504897, 9.76118095489748 19.044074173710932, 9.824909677983872 19.029214719596773, 9.889473807779948 19.01855513862619, 9.954596870769857 19.0121410767614, 10.02 19.01, 10.085403129230142 19.0121410767614, 10.15052619222005 19.01855513862619, 10.215090322016128 19.029214719596773, 10.27881904510252 19.044074173710936, 10.34143946530316 19.063069870504897, 10.40268343236509 19.086120467488715, 10.462288690219001 19.113127258467316, 10.52 19.143974596215564, 10.5755702330196 19.178530387697457, 10.62876142900872 19.216646659708765, 10.679345815100069 19.258160192521025, 10.727106781186547 19.302893218813455, 10.771839807478976 19.350654184899934, 10.813353340291235 19.401238570991282, 10.851469612302544 19.4544297669804, 10.886025403784439 19.51, 10.916872741532687 19.567711309781, 10.943879532511286 19.62731656763491, 10.966930129495106 19.68856053469684, 10.985925826289067 19.75118095489748, 11.00078528040323 19.814909677983874, 11.01144486137381 19.87947380777995, 11.017858923238602 19.944596870769857, 11.02 20.01)) +POLYGON ((11.02 20.01, 11.017858923238 20.075403129230, 11.011444861373 20.140526192220, 11.000785280403 20.205090322016, 10.985925826289 20.268819045102, 10.966930129495 20.331439465303, 10.943879532511 20.392683432365, 10.916872741532 20.452288690219, 10.886025403784 20.51, 10.851469612302 20.565570233019, 10.813353340291 20.618761429008, 10.771839807478 20.669345815100, 10.727106781186 20.717106781186, 10.679345815100 20.761839807478, 10.628761429008 20.803353340291, 10.575570233019 20.841469612302, 10.52 20.876025403784, 10.462288690219 20.906872741532, 10.402683432365 20.933879532511, 10.341439465303 20.956930129495, 10.278819045102 20.975925826289, 10.215090322016 20.990785280403, 10.150526192220 21.001444861373, 10.085403129230 21.007858923238, 10.02 21.01, 9.954596870769 21.007858923238, 9.889473807779 21.001444861373, 9.824909677983 20.990785280403, 9.761180954897 20.975925826289, 9.698560534696 20.956930129495, 9.637316567634 20.933879532511, 9.577711309780 20.906872741532, 9.52 20.876025403784, 9.464429766980 20.841469612302, 9.411238570991 20.803353340291, 9.360654184899 20.761839807478, 9.312893218813 20.717106781186, 9.268160192521 20.669345815100, 9.226646659708 20.618761429008, 9.188530387697 20.565570233019, 9.153974596215 20.51, 9.123127258467 20.452288690219, 9.096120467488 20.392683432365, 9.073069870504 20.331439465303, 9.054074173710 20.268819045102, 9.039214719596 20.205090322016, 9.028555138626 20.140526192220, 9.022141076761 20.075403129230, 9.02 20.01, 9.022141076761 19.944596870769, 9.028555138626 19.879473807779, 9.039214719596 19.814909677983, 9.054074173710 19.751180954897, 9.073069870504 19.688560534696, 9.096120467488 19.627316567634, 9.123127258467 19.567711309781, 9.153974596215 19.51, 9.188530387697 19.454429766980, 9.226646659708 19.401238570991, 9.268160192521 19.350654184899, 9.312893218813 19.302893218813, 9.360654184899 19.258160192521, 9.411238570991 19.216646659708, 9.464429766980 19.178530387697, 9.52 19.143974596215, 9.577711309780 19.113127258467, 9.637316567634 19.086120467488, 9.698560534696 19.063069870504, 9.761180954897 19.044074173710, 9.824909677983 19.029214719596, 9.889473807779 19.018555138626, 9.954596870769 19.012141076761, 10.02 19.01, 10.085403129230 19.012141076761, 10.150526192220 19.018555138626, 10.215090322016 19.029214719596, 10.278819045102 19.044074173710, 10.341439465303 19.063069870504, 10.402683432365 19.086120467488, 10.462288690219 19.113127258467, 10.52 19.143974596215, 10.575570233019 19.178530387697, 10.628761429008 19.216646659708, 10.679345815100 19.258160192521, 10.727106781186 19.302893218813, 10.771839807478 19.350654184899, 10.813353340291 19.401238570991, 10.851469612302 19.454429766980, 10.886025403784 19.51, 10.916872741532 19.567711309781, 10.943879532511 19.627316567634, 10.966930129495 19.688560534696, 10.985925826289 19.751180954897, 11.000785280403 19.814909677983, 11.011444861373 19.879473807779, 11.017858923238 19.944596870769, 11.02 20.01)) PREHOOK: query: SELECT ST_AsText(ST_ConvexHull(geom, ST_Point(0, 1), ST_Point(1, 1))) from geom_binary where id = 14 @@ -379,7 +379,7 @@ where id = 14 POSTHOOK: type: QUERY POSTHOOK: Input: default@geom_binary #### A masked pattern was here #### -POLYGON ((5 6, 0 1, 1 1, 5 6)) +POLYGON ((0 1, 1 1, 5 6, 0 1)) PREHOOK: query: select ST_IsClosed(geom) from geom_binary where id = 16 OR id = 17 OR id =18 @@ -409,7 +409,7 @@ where id = 16 OR id = 17 POSTHOOK: type: QUERY POSTHOOK: Input: default@geom_binary #### A masked pattern was here #### -false POINT (10 10) POINT (20 20) 9.219544457292887 false +false POINT (10 10) POINT (20 20) 9.219544457292 false true POINT (0 0) POINT (0 0) 0.0 false PREHOOK: query: select ST_Equals(ST_LineFromWKB(ST_AsBinary(geom)), ST_GeomFromText('linestring (11 12, 21 23)')), ST_Equals(ST_LineFromWKB(ST_AsBinary(geom)), ST_GeomFromText('linestring (10 10, 20 20)')) @@ -524,4 +524,4 @@ POSTHOOK: query: SELECT ST_DistanceSphere(ST_Point(19.04125, 47.496603), ST_Poin POSTHOOK: type: QUERY POSTHOOK: Input: _dummy_database@_dummy_table #### A masked pattern was here #### -1.2356870285436917E7 +1.235687028543E7 diff --git a/serde/pom.xml b/serde/pom.xml index 5c4f638213ed..f68c13b838e5 100644 --- a/serde/pom.xml +++ b/serde/pom.xml @@ -236,6 +236,12 @@ compile ${esri.version} + + org.locationtech.jts + jts-core + ${jts.version} + compile + org.mockito mockito-core diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryJsonDeserializer.java b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryJsonDeserializer.java index 3ccf8607f8d9..b55e9f39aa61 100755 --- a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryJsonDeserializer.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryJsonDeserializer.java @@ -17,27 +17,51 @@ */ package org.apache.hadoop.hive.serde2.esriJson.deserializer; -import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.MapGeometry; +import com.esri.core.geometry.ogc.OGCGeometry; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.io.WKBReader; import java.io.IOException; /** * - * Deserializes a JSON geometry definition into a Geometry instance + * Deserializes a JSON geometry definition into a JTS Geometry instance. + * Uses the ESRI API to parse Esri JSON, then converts to JTS via WKB round-trip. */ public class GeometryJsonDeserializer extends JsonDeserializer { + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + public GeometryJsonDeserializer() { } @Override public Geometry deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException, JsonProcessingException { - return GeometryEngine.jsonToGeometry(arg0).getGeometry(); + try { + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(arg0); + if (mapGeom == null || mapGeom.getGeometry() == null) { + return null; + } + OGCGeometry ogcGeom = OGCGeometry.createFromEsriGeometry( + mapGeom.getGeometry(), mapGeom.getSpatialReference()); + java.nio.ByteBuffer wkbBuf = ogcGeom.asBinary(); + byte[] wkbBytes = new byte[wkbBuf.remaining()]; + wkbBuf.get(wkbBytes); + Geometry jtsGeom = new WKBReader(GEOMETRY_FACTORY).read(wkbBytes); + if (mapGeom.getSpatialReference() != null) { + jtsGeom.setSRID(mapGeom.getSpatialReference().getID()); + } + return jtsGeom; + } catch (Exception e) { + return null; + } } } diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryTypeJsonDeserializer.java b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryTypeJsonDeserializer.java index 7b93f5e0b85d..7a98d655f782 100755 --- a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryTypeJsonDeserializer.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryTypeJsonDeserializer.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hive.serde2.esriJson.deserializer; -import com.esri.core.geometry.Geometry; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; @@ -27,32 +26,26 @@ /** * - * Deserializes a JSON geometry type enumeration into a Geometry.Type.* enumeration + * Deserializes a JSON geometry type string (e.g. "esriGeometryPolygon") into a String. + * The raw Esri geometry type string is preserved as-is for downstream use. */ -public class GeometryTypeJsonDeserializer extends JsonDeserializer { +public class GeometryTypeJsonDeserializer extends JsonDeserializer { public GeometryTypeJsonDeserializer() { } @Override - public Geometry.Type deserialize(JsonParser parser, DeserializationContext arg1) + public String deserialize(JsonParser parser, DeserializationContext arg1) throws IOException, JsonProcessingException { - String type_text = parser.getText(); - - // geometry type enumerations coming from the JSON are prepended with "esriGeometry" (i.e. esriGeometryPolygon) - // while the geometry-java-api uses the form Geometry.Type.Polygon - if (type_text.startsWith("esriGeometry")) { - // cut out esriGeometry to match Geometry.Type enumeration values - type_text = type_text.substring(12); - - try { - return Enum.valueOf(Geometry.Type.class, type_text); - } catch (Exception e) { - // parsing failed, fall through to unknown geometry type - } + if (type_text == null) { + return "esriGeometryUnknown"; } - - return Geometry.Type.Unknown; + // Preserve the raw esriGeometry* string (e.g. "esriGeometryPolygon"). + // If the value is not already prefixed, normalise it. + if (!type_text.startsWith("esriGeometry")) { + return "esriGeometry" + type_text; + } + return type_text; } } diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/SpatialReferenceJsonDeserializer.java b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/SpatialReferenceJsonDeserializer.java index a1e38ee04df0..85460b3cbc8a 100755 --- a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/SpatialReferenceJsonDeserializer.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/SpatialReferenceJsonDeserializer.java @@ -17,9 +17,9 @@ */ package org.apache.hadoop.hive.serde2.esriJson.deserializer; -import com.esri.core.geometry.SpatialReference; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -27,20 +27,39 @@ /** * - * Deserializes a JSON spatial reference definition into a SpatialReference instance + * Deserializes a JSON spatial reference definition into an integer SRID (WKID). + * Reads the "wkid" field from the spatialReference JSON object. + * Returns 0 if the spatial reference is absent or cannot be parsed. */ -public class SpatialReferenceJsonDeserializer extends JsonDeserializer { +public class SpatialReferenceJsonDeserializer extends JsonDeserializer { public SpatialReferenceJsonDeserializer() { } @Override - public SpatialReference deserialize(JsonParser parser, DeserializationContext arg1) + public Integer deserialize(JsonParser parser, DeserializationContext arg1) throws IOException, JsonProcessingException { try { - return SpatialReference.fromJson(parser); + // The parser is currently positioned at the START_OBJECT token for spatialReference. + // Walk through the object looking for the "wkid" field. + int wkid = 0; + JsonToken token = parser.getCurrentToken(); + if (token == JsonToken.START_OBJECT) { + token = parser.nextToken(); + } + while (token != null && token != JsonToken.END_OBJECT) { + if (token == JsonToken.FIELD_NAME) { + String fieldName = parser.getCurrentName(); + parser.nextToken(); + if ("wkid".equalsIgnoreCase(fieldName) || "latestWkid".equalsIgnoreCase(fieldName)) { + wkid = parser.getIntValue(); + } + } + token = parser.nextToken(); + } + return wkid; } catch (Exception e) { - return null; + return 0; } } } diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryJsonSerializer.java b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryJsonSerializer.java index a653057819a2..8f0123997344 100644 --- a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryJsonSerializer.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryJsonSerializer.java @@ -17,19 +17,32 @@ */ package org.apache.hadoop.hive.serde2.esriJson.serializer; -import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.ogc.OGCGeometry; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.WKBWriter; import java.io.IOException; +/** + * Serializes a JTS Geometry to Esri JSON format. + * Converts JTS -> ESRI via WKB round-trip, then uses GeometryEngine.geometryToJson(). + */ public class GeometryJsonSerializer extends JsonSerializer { @Override public void serialize(Geometry geometry, JsonGenerator jsonGenerator, SerializerProvider arg2) throws IOException { - - jsonGenerator.writeRawValue(GeometryEngine.geometryToJson(null, geometry)); + try { + byte[] wkb = new WKBWriter().write(geometry); + OGCGeometry ogcGeom = OGCGeometry.fromBinary(java.nio.ByteBuffer.wrap(wkb)); + com.esri.core.geometry.Geometry esriGeom = ogcGeom.getEsriGeometry(); + int wkid = geometry.getSRID(); + jsonGenerator.writeRawValue(GeometryEngine.geometryToJson(wkid, esriGeom)); + } catch (Exception e) { + jsonGenerator.writeNull(); + } } } diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryTypeJsonSerializer.java b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryTypeJsonSerializer.java index 1c7932bce621..5775b5a9319c 100644 --- a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryTypeJsonSerializer.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryTypeJsonSerializer.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hive.serde2.esriJson.serializer; -import com.esri.core.geometry.Geometry.Type; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; @@ -25,11 +24,24 @@ import java.io.IOException; -public class GeometryTypeJsonSerializer extends JsonSerializer { +/** + * Serializes a geometry type String (e.g. "esriGeometryPolygon") to JSON. + * The String is already in the Esri "esriGeometry*" form as produced by + * GeometryTypeJsonDeserializer; it is written verbatim. + */ +public class GeometryTypeJsonSerializer extends JsonSerializer { @Override - public void serialize(Type geometryType, JsonGenerator jsonGenerator, SerializerProvider arg2) + public void serialize(String geometryType, JsonGenerator jsonGenerator, SerializerProvider arg2) throws IOException, JsonProcessingException { - jsonGenerator.writeString("esriGeometry" + geometryType); + if (geometryType == null) { + jsonGenerator.writeNull(); + } else if (geometryType.startsWith("esriGeometry")) { + // Already in canonical form — write as-is + jsonGenerator.writeString(geometryType); + } else { + // Normalise bare type names (e.g. "Polygon" -> "esriGeometryPolygon") + jsonGenerator.writeString("esriGeometry" + geometryType); + } } } diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/SpatialReferenceJsonSerializer.java b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/SpatialReferenceJsonSerializer.java index 66e32364da8b..295e9c3a6fa9 100644 --- a/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/SpatialReferenceJsonSerializer.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/SpatialReferenceJsonSerializer.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hive.serde2.esriJson.serializer; -import com.esri.core.geometry.SpatialReference; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; @@ -25,17 +24,16 @@ import java.io.IOException; -public class SpatialReferenceJsonSerializer extends JsonSerializer { +/** + * Serializes an integer SRID (WKID) as a JSON spatialReference object: {"wkid": }. + */ +public class SpatialReferenceJsonSerializer extends JsonSerializer { @Override - public void serialize(SpatialReference spatialReference, JsonGenerator jsonGenerator, SerializerProvider arg2) + public void serialize(Integer wkid, JsonGenerator jsonGenerator, SerializerProvider arg2) throws IOException, JsonProcessingException { - - int wkid = spatialReference.getID(); - jsonGenerator.writeStartObject(); - jsonGenerator.writeObjectField("wkid", wkid); + jsonGenerator.writeObjectField("wkid", wkid == null ? 0 : wkid); jsonGenerator.writeEndObject(); } - }