Skip to content

Commit cf01102

Browse files
[GH-1996] Create object of S2Geography, and implement Point/Polyline/PolygonGeography with its encoder/decoder (#1992)
* Create object of S2Geography, and implement PoinGeography with its encoder/decoder * Add POLYLINE implementation on S2Geography * Add POLYGON implements on S2Geography * Match coding style * "Apply Spotless formatting to PolylineGeographyTest" * Redesign of S2Geography - Import org.datasyslab s2-geometry-library - Clean up S2Geography abstract design - Update Encode/Decode inside each kind of geography * clean up unnecessary files in current branch * Refine design of EncodeTagged in S2Geography - Adding back EncodeTagged in S2Geography - Let each geography type calls its own encode / decode function - Change to use Kyro UnsafeInput and UnsafeOutput * Modify encoder() and add new test cases * clean up code of encode and clarify comments * Update POLYGON to only take one polygon * Remove S2Regionwrapper & S2Shapewrapper * clean up minor issue * resolve minor issue with PolygonGeography
1 parent 319e8ee commit cf01102

11 files changed

Lines changed: 1398 additions & 2 deletions

File tree

common/pom.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,13 @@
8181
<groupId>org.locationtech.spatial4j</groupId>
8282
<artifactId>spatial4j</artifactId>
8383
</dependency>
84+
<!-- org.datasyslab:s2-geometry-library is a fork of com.google.geometry:s2-geometry-library-->
85+
<!-- as implementation requirements of apache sedona issue link: -->
86+
<!-- https://github.com/apache/sedona/issues/1996 -->
8487
<dependency>
85-
<groupId>com.google.geometry</groupId>
86-
<artifactId>s2-geometry</artifactId>
88+
<groupId>org.datasyslab</groupId>
89+
<artifactId>s2-geometry-library</artifactId>
90+
<version>20250620-rc1</version>
8791
</dependency>
8892
<dependency>
8993
<groupId>com.uber</groupId>
@@ -159,6 +163,7 @@
159163
<include>it.geosolutions.jaiext.jiffle:*</include>
160164
<include>org.antlr:*</include>
161165
<include>org.codehaus.janino:*</include>
166+
<include>org.datasyslab:s2-geometry-library</include>
162167
</includes>
163168
</artifactSet>
164169
<relocations>
@@ -177,6 +182,10 @@
177182
<pattern>org.codehaus</pattern>
178183
<shadedPattern>org.apache.sedona.shaded.codehaus</shadedPattern>
179184
</relocation>
185+
<relocation>
186+
<pattern>com.google.common.geometry</pattern>
187+
<shadedPattern>org.apache.sedona.shaded.s2</shadedPattern>
188+
</relocation>
180189
</relocations>
181190
<filters>
182191
<!-- filter to address "Invalid signature file" issue - see http://stackoverflow.com/a/6743609/589215 -->
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sedona.common.S2Geography;
20+
21+
public class EncodeOptions {
22+
/** FAST writes raw doubles; COMPACT snaps vertices to cell centers. */
23+
public enum CodingHint {
24+
FAST,
25+
COMPACT
26+
}
27+
28+
/** Default: FAST. */
29+
private CodingHint codingHint = CodingHint.FAST;
30+
31+
/** If true, convert “hard” shapes into lazy‐decodable variants. */
32+
private boolean enableLazyDecode = false;
33+
34+
/** If true, prefix the payload with the cell‐union covering. */
35+
private boolean includeCovering = false;
36+
37+
public EncodeOptions() {}
38+
39+
/** Control FAST vs. COMPACT encoding. */
40+
public void setCodingHint(CodingHint hint) {
41+
this.codingHint = hint;
42+
}
43+
44+
public CodingHint getCodingHint() {
45+
return codingHint;
46+
}
47+
48+
/** Enable or disable lazy‐decode conversions. */
49+
public void setEnableLazyDecode(boolean enable) {
50+
this.enableLazyDecode = enable;
51+
}
52+
53+
public boolean isEnableLazyDecode() {
54+
return enableLazyDecode;
55+
}
56+
57+
/** Include or omit the cell‐union covering prefix. */
58+
public void setIncludeCovering(boolean include) {
59+
this.includeCovering = include;
60+
}
61+
62+
public boolean isIncludeCovering() {
63+
return includeCovering;
64+
}
65+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sedona.common.S2Geography;
20+
21+
import com.esotericsoftware.kryo.io.Input;
22+
import com.esotericsoftware.kryo.io.Output;
23+
import com.esotericsoftware.kryo.io.UnsafeInput;
24+
import com.google.common.geometry.S2CellId;
25+
import java.io.*;
26+
import java.util.List;
27+
import org.apache.sedona.common.S2Geography.S2Geography.GeographyKind;
28+
29+
/**
30+
* A 4 byte prefix for encoded geographies. Builds a 5-byte header (EncodeTag) containing 1 byte:
31+
* kind 1 byte: flags 1 byte: coveringSize 1 byte: reserved (must be 0)
32+
*/
33+
public class EncodeTag {
34+
/**
35+
* Subclass of S2Geography whose decode() method will be invoked. Encoded using a single unsigned
36+
* byte (represented as an int in Java, range 0–255).
37+
*/
38+
private GeographyKind kind = GeographyKind.UNINITIALIZED;
39+
/**
40+
* Flags for encoding metadata. one flag {@code kFlagEmpty} is supported, which is set if and only
41+
* if the geography contains zero shapes. second flag {@code FlagCompact}, which is set if user
42+
* set COMPACT encoding type
43+
*/
44+
private byte flags = 0;
45+
// ——— Bit‐masks for our one‐byte flags field ———————————————————
46+
/** set if geography has zero shapes */
47+
public static final byte FLAG_EMPTY = 1 << 0;
48+
/** set if using COMPACT coding; if clear, we’ll treat as FAST */
49+
public static final byte FLAG_COMPACT = 1 << 1;
50+
// bits 2–7 are still unused (formerly “reserved”)
51+
/**
52+
* Number of S2CellId entries that follow this tag. A value of zero (i.e., an empty covering)
53+
* means no covering was written, but this does not imply that the geography itself is empty.
54+
*/
55+
private byte coveringSize = 0;
56+
/** Reserved byte for future use. Must be set to 0. */
57+
private byte reserved = 0;
58+
59+
// ——— Write the 4-byte tag header ——————————————————————————————————————
60+
public EncodeTag() {}
61+
62+
public EncodeTag(EncodeOptions opts) {
63+
if (opts.getCodingHint() == EncodeOptions.CodingHint.COMPACT) {
64+
flags |= FLAG_COMPACT;
65+
}
66+
}
67+
/** Write exactly 4 bytes: [kind|flags|coveringSize|reserved]. */
68+
public void encode(Output out) throws IOException {
69+
out.writeByte(kind.getKind());
70+
out.writeByte(flags);
71+
out.writeByte(coveringSize);
72+
out.writeByte(reserved);
73+
}
74+
// ——— Read it back ————————————————————————————————————————————————
75+
76+
/** Reads exactly 4 bytes (in the same order) from the stream. */
77+
public static EncodeTag decode(Input in) throws IOException {
78+
EncodeTag tag = new EncodeTag();
79+
tag.kind = GeographyKind.fromKind(in.readByte());
80+
tag.flags = in.readByte();
81+
tag.coveringSize = in.readByte();
82+
tag.reserved = in.readByte();
83+
if (tag.reserved != 0)
84+
throw new IOException("Reserved header byte must be 0, was " + tag.reserved);
85+
return tag;
86+
}
87+
88+
// ——— Helpers for the optional covering list —————————————————————————
89+
90+
/** Read coveringSize many cell-ids and add them to cellIds. */
91+
public void decodeCovering(UnsafeInput in, List<S2CellId> cellIds) throws IOException {
92+
int count = coveringSize & 0xFF;
93+
for (int i = 0; i < count; i++) {
94+
long id = in.readLong();
95+
cellIds.add(new S2CellId(id));
96+
}
97+
}
98+
99+
/** Skip over coveringSize many cell-ids in the stream. */
100+
public void skipCovering(UnsafeInput in) throws IOException {
101+
int count = coveringSize & 0xFF;
102+
for (int i = 0; i < count; i++) {
103+
in.readLong();
104+
}
105+
}
106+
107+
/** Ensure we didn’t accidentally write a non-zero reserved byte. */
108+
public void validate() {
109+
if (reserved != 0) {
110+
throw new IllegalStateException("EncodeTag.reserved must be 0, was " + (reserved & 0xFF));
111+
}
112+
}
113+
114+
// ——— Getters / setters ——————————————————————————————————————————
115+
116+
public GeographyKind getKind() {
117+
return this.kind;
118+
}
119+
120+
public void setKind(GeographyKind kind) {
121+
this.kind = kind;
122+
}
123+
124+
public byte getFlags() {
125+
return flags;
126+
}
127+
128+
public void setFlags(byte flags) {
129+
this.flags = flags;
130+
}
131+
132+
public byte getCoveringSize() {
133+
return coveringSize;
134+
}
135+
136+
public void setCoveringSize(byte size) {
137+
this.coveringSize = size;
138+
}
139+
140+
/** mark or unmark the EMPTY flag */
141+
public void setEmpty(boolean empty) {
142+
if (empty) flags |= FLAG_EMPTY;
143+
else flags &= ~FLAG_EMPTY;
144+
}
145+
146+
/** choose COMPACT (true) or FAST (false) */
147+
public void setCompact(boolean compact) {
148+
if (compact) flags |= FLAG_COMPACT;
149+
else flags &= ~FLAG_COMPACT;
150+
}
151+
152+
public boolean isEmpty() {
153+
return (flags & FLAG_EMPTY) != 0;
154+
}
155+
156+
public boolean isCompact() {
157+
return (flags & FLAG_COMPACT) != 0;
158+
}
159+
160+
public boolean isFast() {
161+
return !isCompact();
162+
}
163+
}

0 commit comments

Comments
 (0)