Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
<esri.version>2.2.4</esri.version>
<flatbuffers.version>1.12.0</flatbuffers.version>
<jts.version>1.20.0</jts.version>
<geohash.version>1.4.0</geohash.version>
<guava.version>22.0</guava.version>
<groovy.version>2.4.21</groovy.version>
<h2database.version>2.2.220</h2database.version>
Expand Down
7 changes: 7 additions & 0 deletions ql/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,12 @@
<version>${jts.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.hsr</groupId>
<artifactId>geohash</artifactId>
<version>${geohash.version}</version>
<scope>compile</scope>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

</dependency>
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>RoaringBitmap</artifactId>
Expand Down Expand Up @@ -1154,6 +1160,7 @@
<include>org.locationtech.jts:jts-core</include>
<include>org.locationtech.jts.io:jts-io-common</include>
<include>com.esri.geometry:esri-geometry-api</include>
<include>ch.hsr:geohash</include>
<include>org.apache.tez:tez-protobuf-history-plugin</include>
</includes>
</artifactSet>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
import org.apache.hadoop.hive.ql.udf.esri.ST_GeomFromGeoJson;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeomFromJson;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeomFromShape;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeomFromGeoHash;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeoHash;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeomFromText;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeomFromWKB;
import org.apache.hadoop.hive.ql.udf.esri.ST_GeometryN;
Expand Down Expand Up @@ -737,6 +739,8 @@ public final class FunctionRegistry {
system.registerFunction("ST_GeomFromGeoJson", ST_GeomFromGeoJson.class);
system.registerFunction("ST_GeomFromJson", ST_GeomFromJson.class);
system.registerFunction("ST_GeomFromShape", ST_GeomFromShape.class);
system.registerFunction("ST_GeomFromGeoHash", ST_GeomFromGeoHash.class);
system.registerFunction("ST_GeoHash", ST_GeoHash.class);
system.registerFunction("ST_GeomFromText", ST_GeomFromText.class);
system.registerFunction("ST_GeomFromWKB", ST_GeomFromWKB.class);
system.registerFunction("ST_GeometryType", ST_GeometryType.class);
Expand Down
94 changes: 94 additions & 0 deletions ql/src/java/org/apache/hadoop/hive/ql/udf/esri/GeoHashUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 ch.hsr.geohash.BoundingBox;
import ch.hsr.geohash.GeoHash;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Polygon;

public final class GeoHashUtils {

public static final int MIN_CHARACTER_PRECISION = 1;

/**
* Maximum geohash length in base-32 characters for {@link GeoHash#geoHashStringWithCharacterPrecision}.
*/
public static final int MAX_CHARACTER_PRECISION = 12;

public static final int DEFAULT_CHARACTER_PRECISION = 12;

private GeoHashUtils() {
}

public static String geohashForPoint(double longitude, double latitude, int characterPrecision) {
return GeoHash.geoHashStringWithCharacterPrecision(latitude, longitude, characterPrecision);
}

/**
* Returns a rectangular polygon for the geohash cell (closed ring, lon/lat coordinates).
*
* @param geohash base-32 geohash string (non-empty)
* @param characterPrecision number of leading characters to use (1–12, at most {@code geohash}
* length)
*/
public static Polygon geohashCellPolygon(String geohash, int characterPrecision) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why this is the only public method in this class that got javadoc. I would suggest adding documentation to the other public methods as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for other methods their names are suffieciently self explainatory, this one required some explaination regarding type of polygon returned and geohash string kind needed so added javadoc for it only. I think we can avoid javadoc for others wdyt?

if (geohash == null || geohash.isEmpty()) {
return null;
}
if (characterPrecision < MIN_CHARACTER_PRECISION ||
characterPrecision > MAX_CHARACTER_PRECISION ||
characterPrecision > geohash.length()) {
return null;
}
String hashPrefix = geohash.substring(0, characterPrecision);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand what happens here: we receive a geohash and a precision.

And we only use the first part of the hash.
That suggests a geohash actually contains geo coordinates only in the first n character, according to precision. What is in the remaining part of the geohash string?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is postgis compliant behaviour
a geohash while converting to geometry implies only to a rectangular polygon basically so size of rectangle depends on the no. of characters used from geohash string so if you use entire string then that rectangle contains less other points than the one from it is constructed, and if you use subset prefix of it then it will contain more other points tahn the one from it is constructed

BoundingBox box = GeoHash.fromGeohashString(hashPrefix).getBoundingBox();
double west = box.getWestLongitude();
double east = box.getEastLongitude();
double south = box.getSouthLatitude();
double north = box.getNorthLatitude();
// Closed ring (west,south) -> (west,north) -> (east,north) -> (east,south) -> close.
Coordinate[] ring = new Coordinate[] {
new Coordinate(west, south),
new Coordinate(west, north),
new Coordinate(east, north),
new Coordinate(east, south),
new Coordinate(west, south)
};
return GeometryUtils.GEOMETRY_FACTORY.createPolygon(ring);
}

public static int resolveEncodePrecision(Integer precisionArg) {
int precision = precisionArg == null ? DEFAULT_CHARACTER_PRECISION : precisionArg;
if (precision < MIN_CHARACTER_PRECISION || precision > MAX_CHARACTER_PRECISION) {
return -1;
}
return precision;
}

public static int resolveDecodePrecision(Integer precisionArg, int geohashLength) {
int precision = precisionArg == null ? geohashLength : precisionArg;
if (precision < MIN_CHARACTER_PRECISION ||
precision > geohashLength ||
geohashLength > MAX_CHARACTER_PRECISION) {
return -1;
}
return precision;
}
}
30 changes: 21 additions & 9 deletions ql/src/java/org/apache/hadoop/hive/ql/udf/esri/LogUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,23 @@
private static final int MSG_EXCEPTION_THROWN = 9;
private static final int MSG_NOT_3D = 10;
private static final int MSG_NOT_MEASURED = 11;

private static final String[] messages =
{ "Mismatched spatial references ('%d' <> '%d')", "Invalid arguments - one or more arguments are null.",
"Invalid arguments. Expecting one or more x,y pairs.",
"Invalid arguments. Expecting one or more x,y pairs in array argument %d.",
"Invalid geometry type. Expecting %s but found %s", "Invalid arguments. Ill-formed text: %s ....",
"Invalid index. Expected range [%d, %d], actual index %d.", "Internal error - %s.",
"Invalid arguments. Expecting one or more arguments.", "Exception thrown by %s", "Invalid argument - not 3D",
"Invalid argument - not measured" };
private static final int MSG_INVALID_PRECISION = 12;

private static final String[] messages = {

Check warning on line 40 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/LogUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Name 'messages' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhDY2cvIPkhEVep0&open=AaDLAhDY2cvIPkhEVep0&pullRequest=6813
"Mismatched spatial references ('%d' <> '%d')",
"Invalid arguments - one or more arguments are null.",
"Invalid arguments. Expecting one or more x,y pairs.",
"Invalid arguments. Expecting one or more x,y pairs in array argument %d.",
"Invalid geometry type. Expecting %s but found %s",
"Invalid arguments. Ill-formed text: %s ....",
"Invalid index. Expected range [%d, %d], actual index %d.",
"Internal error - %s.",
"Invalid arguments. Expecting one or more arguments.",
"Exception thrown by %s",
"Invalid argument - not 3D",
"Invalid argument - not measured",
"Invalid precision - Precision must be between %d and %d"
};

/**
* Log when comparing geometries in different spatial references
Expand Down Expand Up @@ -111,4 +119,8 @@
logger.error(messages[MSG_NOT_MEASURED]);
}

public static void Log_InvalidPrecision(Logger logger, int minPrecision, int maxPrecision) {

Check warning on line 122 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/LogUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Name 'Log_InvalidPrecision' must match pattern '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhDY2cvIPkhEVep1&open=AaDLAhDY2cvIPkhEVep1&pullRequest=6813

Check warning on line 122 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/LogUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this method name to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhDY2cvIPkhEVepz&open=AaDLAhDY2cvIPkhEVepz&pullRequest=6813
logger.error(String.format(messages[MSG_INVALID_PRECISION], minPrecision, maxPrecision));

Check warning on line 123 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/LogUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhDY2cvIPkhEVepy&open=AaDLAhDY2cvIPkhEVepy&pullRequest=6813
}

}
83 changes: 83 additions & 0 deletions ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeoHash.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hadoop.hive.ql.udf.esri;

import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Description(
name = "ST_GeoHash",
value = """
_FUNC_(point) - geohash for a point geometry
_FUNC_(point, precision) - geohash at given character precision
""",
extended = """
SELECT _FUNC_(ST_Point(-126.965375, 43.234528), 12); -- 9pttyydekk4t
""")
public class ST_GeoHash extends ST_Geometry {

Check warning on line 39 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeoHash.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Name 'ST_GeoHash' must match pattern '^[A-Z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhbu2cvIPkhEVep3&open=AaDLAhbu2cvIPkhEVep3&pullRequest=6813

Check warning on line 39 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeoHash.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this class name to match the regular expression '^[A-Z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhbu2cvIPkhEVep2&open=AaDLAhbu2cvIPkhEVep2&pullRequest=6813

static final Logger LOG = LoggerFactory.getLogger(ST_GeoHash.class.getName());

public Text evaluate(BytesWritable geomref) {
return evaluate(geomref, null);
}

public Text evaluate(BytesWritable geomref, IntWritable precisionArg) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think geomref is something like geometric reference. I would suggest using geomRef instead. Both here and at the other evaluate method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same variable name is used in almost all the other geospatial udfs so used same for consistency.
changing it will require changing it in all other places as well

if (geomref == null || geomref.getLength() == 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny suggestion about readability: commons-lang3 has utility methods that helps a lot around strings, like StringUtils.isEmpty().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question about performance vs readability: GeometryUtils.getType() starts with the following check:

    if (geomref == null || geomref.getLength() < 5) {
      return OGCType.UNKNOWN;
    }

This is basically almost the exact same check as the check here. Does it worth to do both checks or one of them is enough?

LogUtils.Log_ArgumentsNull(LOG);
return null;
}

GeometryUtils.OGCType type = GeometryUtils.getType(geomref);
if (type != GeometryUtils.OGCType.ST_POINT) {
LogUtils.Log_InvalidType(LOG, GeometryUtils.OGCType.ST_POINT, type);
return null;
}

Geometry geom = GeometryUtils.geometryFromEsriShape(geomref);
if (geom == null) {
return null;
}
Point point = (Point) geom;
return geohashText(point.getX(), point.getY(), precisionArg);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GeometryUtils.geometryFromEsriShape(geomref) can return with an empty point. Empty point has no coordinates and point.getX() and pont.getY() can return with IllegalStateException if there is no coordinates.
It would worth to do double checking and return with null not only if geometryFromEsriShape but if the point is empty as well (Point.isEmpty()).

}

private Text geohashText(double longitude, double latitude, IntWritable precisionArg) {
int precision =
GeoHashUtils.resolveEncodePrecision(precisionArg == null ? null : precisionArg.get());
if (precision < 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit about readability:
Now we have to navigate to GeoHashUtils and check the method what precision < 0 actually means if we want to understand it.

What if adding an extra contstant to GeoHashUtils, something like INVALID_PRECISION so that this expression can be rewritten to an easier to understand format, like if (precision == GeoHashUtils.INVALID_PRECISION).

LogUtils.Log_InvalidPrecision(LOG, GeoHashUtils.MIN_CHARACTER_PRECISION,
GeoHashUtils.MAX_CHARACTER_PRECISION);
return null;
}
try {
String hash = GeoHashUtils.geohashForPoint(longitude, latitude, precision);
return new Text(hash);
} catch (Exception e) {
LogUtils.Log_InternalError(LOG, "ST_GeoHash: " + e);
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hadoop.hive.ql.udf.esri;

import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.locationtech.jts.geom.Polygon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Description(
name = "ST_GeomFromGeoHash",
value = """
_FUNC_(geohash) - polygon for the geohash cell
_FUNC_(geohash, precision) - polygon using the first precision characters
""",
extended = """
SELECT ST_AsText(_FUNC_('9ptty', 5));
""")
public class ST_GeomFromGeoHash extends ST_Geometry {

Check warning on line 38 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoHash.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Name 'ST_GeomFromGeoHash' must match pattern '^[A-Z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhb22cvIPkhEVep6&open=AaDLAhb22cvIPkhEVep6&pullRequest=6813

Check warning on line 38 in ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoHash.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this class name to match the regular expression '^[A-Z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaDLAhb22cvIPkhEVep4&open=AaDLAhb22cvIPkhEVep4&pullRequest=6813

static final Logger LOG = LoggerFactory.getLogger(ST_GeomFromGeoHash.class.getName());

public BytesWritable evaluate(Text geohashText) {
return evaluate(geohashText, null);
}

public BytesWritable evaluate(Text geohashText, IntWritable precisionArg) {
String geohash = geohashText != null ? geohashText.toString().trim() : null;
if (geohash == null || geohash.isEmpty()) {
LogUtils.Log_ArgumentsNull(LOG);
return null;
}

int characterPrecision = GeoHashUtils.resolveDecodePrecision(precisionArg == null ? null : precisionArg.get(),
geohash.length());
if (characterPrecision < 0) {
LogUtils.Log_InvalidPrecision(LOG, GeoHashUtils.MIN_CHARACTER_PRECISION,
Math.min(geohash.length(), GeoHashUtils.MAX_CHARACTER_PRECISION));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the geohash.length() is 10, there will be the log message:

Invalid precision - Precision must be between 1 and 10

It is not true because the precision should be between 1 and 12.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when we are construct geometry from geoshash string, specifying precision beyond string length doesn't yield anything and will be wrong as per behaviour: #6813 (comment)
so for string of length 10 precison 12 is wrong will be wrong as construction of geometry depends on the no. of chars in the string we are gonna use which are at max 10 in that case

return null;
}

try {
Polygon polygon = GeoHashUtils.geohashCellPolygon(geohash, characterPrecision);
if (polygon == null) {
return null;
}
return GeometryUtils.geometryToEsriShapeBytesWritable(polygon);
} catch (Exception e) {
LogUtils.Log_InternalError(LOG, "ST_GeomFromGeoHash: " + e);
return null;
}
}
}
Loading
Loading