-
Notifications
You must be signed in to change notification settings - Fork 3.4k
API, Core: Add geometry and geography types support #12346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
d9274ee
cccb563
8c1c6d5
73f6022
7bbfb8b
c3b57f7
eb1896a
e78a9c0
8f677a8
1419174
2bbd453
e157ea9
384ef1c
0c451ca
7c8fa2b
cc77321
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| /* | ||
| * 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.iceberg.types; | ||
|
|
||
| import java.util.Locale; | ||
| import org.apache.iceberg.relocated.com.google.common.base.Preconditions; | ||
|
|
||
| /** The algorithm for interpolating edges. */ | ||
| public enum EdgeAlgorithm { | ||
| /** Edges are interpolated as geodesics on a sphere. */ | ||
| SPHERICAL, | ||
| /** See <a href="https://en.wikipedia.org/wiki/Vincenty%27s_formulae">Vincenty's formulae</a> */ | ||
| VINCENTY, | ||
| /** | ||
| * Thomas, Paul D. Spheroidal geodesics, reference systems, & local geometry. US Naval | ||
| * Oceanographic Office, 1970. | ||
| */ | ||
| THOMAS, | ||
| /** | ||
| * Thomas, Paul D. Mathematical models for navigation systems. US Naval Oceanographic Office, | ||
| * 1965. | ||
| */ | ||
| ANDOYER, | ||
| /** | ||
| * <a href="https://link.springer.com/content/pdf/10.1007/s00190-012-0578-z.pdf">Karney, Charles | ||
| * FF. "Algorithms for geodesics." Journal of Geodesy 87 (2013): 43-55 </a>, and <a | ||
| * href="https://geographiclib.sourceforge.io/">GeographicLib</a>. | ||
| */ | ||
| KARNEY; | ||
|
|
||
| public static EdgeAlgorithm fromName(String algorithmName) { | ||
| Preconditions.checkNotNull(algorithmName, "Invalid edge interpolation algorithm: null"); | ||
| try { | ||
| return EdgeAlgorithm.valueOf(algorithmName.toUpperCase(Locale.ENGLISH)); | ||
| } catch (IllegalArgumentException e) { | ||
| throw new IllegalArgumentException( | ||
| String.format("Invalid edge interpolation algorithm: %s", algorithmName), e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return name().toLowerCase(Locale.ENGLISH); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,9 +58,16 @@ private Types() {} | |
| .put(BinaryType.get().toString(), BinaryType.get()) | ||
| .put(UnknownType.get().toString(), UnknownType.get()) | ||
| .put(VariantType.get().toString(), VariantType.get()) | ||
| .put(GeometryType.crs84().toString(), GeometryType.crs84()) | ||
| .put(GeographyType.crs84().toString(), GeographyType.crs84()) | ||
| .buildOrThrow(); | ||
|
|
||
| private static final Pattern FIXED = Pattern.compile("fixed\\[\\s*(\\d+)\\s*\\]"); | ||
| private static final Pattern GEOMETRY_PARAMETERS = | ||
| Pattern.compile("geometry\\s*(?:\\(\\s*([^,]+?)\\s*\\))?", Pattern.CASE_INSENSITIVE); | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| private static final Pattern GEOGRAPHY_PARAMETERS = | ||
| Pattern.compile( | ||
| "geography\\s*(?:\\(\\s*([^,]+)\\s*(?:,\\s*(\\w*)\\s*)?\\))?", Pattern.CASE_INSENSITIVE); | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| private static final Pattern DECIMAL = | ||
| Pattern.compile("decimal\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)"); | ||
|
|
||
|
|
@@ -70,6 +77,21 @@ public static Type fromTypeName(String typeString) { | |
| return TYPES.get(lowerTypeString); | ||
| } | ||
|
|
||
| Matcher geometry = GEOMETRY_PARAMETERS.matcher(typeString); | ||
| if (geometry.matches()) { | ||
| String crs = geometry.group(1); | ||
| return GeometryType.of(crs != null ? crs.trim() : null); | ||
| } | ||
|
|
||
| Matcher geography = GEOGRAPHY_PARAMETERS.matcher(typeString); | ||
| if (geography.matches()) { | ||
| String crs = geography.group(1); | ||
| String algorithmName = geography.group(2); | ||
| EdgeAlgorithm algorithm = | ||
| algorithmName == null ? null : EdgeAlgorithm.fromName(algorithmName.trim()); | ||
| return GeographyType.of(crs != null ? crs.trim() : null, algorithm); | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| Matcher fixed = FIXED.matcher(lowerTypeString); | ||
| if (fixed.matches()) { | ||
| return FixedType.ofLength(Integer.parseInt(fixed.group(1))); | ||
|
|
@@ -543,6 +565,148 @@ public int hashCode() { | |
| } | ||
| } | ||
|
|
||
| public static class GeometryType extends PrimitiveType { | ||
|
|
||
| private final String crs; | ||
|
|
||
| private GeometryType(String crs) { | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| if (crs != null) { | ||
| Preconditions.checkArgument(!crs.isEmpty(), "Invalid CRS: (empty string)"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the extra parens around 'empty string'?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is following the existing error message style: https://github.com/apache/iceberg/blob/apache-iceberg-1.8.1/core/src/main/java/org/apache/iceberg/view/BaseView.java#L116, I thought that it is a convention to add parenthesis around empty string. |
||
| Preconditions.checkArgument( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i might have missed this, but why not just trim it?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Constructing a GeometryType manually using
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this check is needed. We don't do similar validations elsewhere. The responsibility of this class is to pass the incoming CRS without modification. There's no need to validate the string. Parsing should handle that.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed trim. Trimming is handled by regex when parsing primitive type strings. |
||
| crs.trim().equals(crs), "CRS must not have leading or trailing spaces: '%s'", crs); | ||
| this.crs = crs; | ||
| } else { | ||
| this.crs = null; | ||
| } | ||
| } | ||
|
|
||
| private GeometryType() { | ||
| crs = null; | ||
| } | ||
|
|
||
| public static GeometryType crs84() { | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| return new GeometryType(); | ||
| } | ||
|
|
||
| public static GeometryType of(String crs) { | ||
| return new GeometryType(crs); | ||
| } | ||
|
|
||
| @Override | ||
| public TypeID typeId() { | ||
| return TypeID.GEOMETRY; | ||
| } | ||
|
|
||
| public String crs() { | ||
|
szehon-ho marked this conversation as resolved.
|
||
| return crs; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If CRS is null, should this return
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is better to return We can make |
||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } else if (!(o instanceof GeometryType)) { | ||
| return false; | ||
| } | ||
|
|
||
| GeometryType that = (GeometryType) o; | ||
| return Objects.equals(crs, that.crs); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(GeometryType.class, crs); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| if (crs == null) { | ||
| return "geometry"; | ||
| } | ||
|
|
||
| return String.format("geometry(%s)", crs); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using empty string for CRS would produce
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default CRS is stored as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good. The only remaining case is normalizing |
||
| } | ||
| } | ||
|
|
||
| public static class GeographyType extends PrimitiveType { | ||
|
|
||
| public static final String DEFAULT_CRS = "OGC:CRS84"; | ||
|
|
||
| private final String crs; | ||
| private final EdgeAlgorithm algorithm; | ||
|
|
||
| private GeographyType(String crs, EdgeAlgorithm algorithm) { | ||
| if (crs != null) { | ||
| Preconditions.checkArgument(!crs.isEmpty(), "Invalid CRS: (empty string)"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment, i would remove the parens. |
||
| Preconditions.checkArgument( | ||
| crs.trim().equals(crs), "CRS must not have leading or trailing spaces: '%s'", crs); | ||
| this.crs = crs; | ||
| } else { | ||
| this.crs = null; | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| this.algorithm = algorithm; | ||
| } | ||
|
|
||
| private GeographyType() { | ||
| this.crs = null; | ||
| this.algorithm = null; | ||
| } | ||
|
|
||
| public static GeographyType crs84() { | ||
|
rdblue marked this conversation as resolved.
|
||
| return new GeographyType(); | ||
| } | ||
|
|
||
| public static GeographyType forCRS(String crs) { | ||
|
rdblue marked this conversation as resolved.
Outdated
|
||
| return new GeographyType(crs, null); | ||
| } | ||
|
|
||
| public static GeographyType of(String crs, EdgeAlgorithm algorithm) { | ||
| return new GeographyType(crs, algorithm); | ||
| } | ||
|
|
||
| @Override | ||
| public TypeID typeId() { | ||
| return TypeID.GEOGRAPHY; | ||
| } | ||
|
|
||
| public String crs() { | ||
| return crs; | ||
| } | ||
|
|
||
| public EdgeAlgorithm algorithm() { | ||
| return algorithm; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } else if (!(o instanceof GeographyType)) { | ||
| return false; | ||
| } | ||
|
|
||
| GeographyType that = (GeographyType) o; | ||
| return Objects.equals(crs, that.crs) && Objects.equals(algorithm, that.algorithm); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(GeographyType.class, crs, algorithm); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| if (algorithm != null) { | ||
| return String.format("geography(%s, %s)", crs != null ? crs : DEFAULT_CRS, algorithm); | ||
| } else if (crs != null) { | ||
| return String.format("geography(%s)", crs); | ||
| } else { | ||
| return "geography"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static class NestedField implements Serializable { | ||
| public static NestedField optional(int id, String name, Type type) { | ||
| return new NestedField(true, id, name, type, null, null, null); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍